springboot+springsecurity+mybatis+jwt实现单点登录(详细

2021年奉上我最喜欢的一句话:愿你孤独的努力都有回报,愿你前行的路上有人陪伴。加油????!

项目下载:????

文章目录??一、springsecuriyt基础????二、jwt????Ⅰ、jwt是什么????Ⅱ、为什么要使用jwt????2.1、什么是有状态????2.2、什么是无状态????Ⅲ、jwt的组成????三、springboot+springsecurity+mybatis+jwt实现单点登录????Ⅰ、核心流程分析????Ⅱ、具体代码实现及其分析????1.JwtTokenUtil????2.统一结果返回封装类????3、handler处理类????3.1、AjaxAccessDeniedHandler权限不足处理类????3.2、AjaxAuthenticationEntryPoint匿名无权限处理类????3.3、AjaxAuthenticationFailureHandler认证失败处理类????3.4、AjaxAuthenticationSuccessHandler认证成功处理类????3.5、AjaxLogoutSuccessHandler退出成功处理类????4、AuthenticationFilter(token的验证类)????5、SecurityConfig配置类????6、自定义异常处理类????6.1、RegisterFailureException注册失败异常处理类????6.2、RegisterUsernameHasBeenExists注册时用户名已经存在异常处理类????6.3、Controller层全局异常处理类????7、controller,service,mapper,entity,mapper.xml编写????7.1、controller????7.2、service????7.4、mapper????7.5、mapper.xml????7.6、entity????8、application.yml????9、html测试??


一、springsecuriyt基础


二、jwtⅠ、jwt是什么

JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权。

Ⅱ、为什么要使用jwt

互联网认证方式由两种:无状态和有状态

2.1、什么是有状态

即服务端需要记录每次会话的客户端信息,典型的如session

有状态流程

1.当用户登录后,客户端会保存该用户的信息到服务端的session中,然后返回一个sessionId,将其保存到客户端的Cookie中

2.当下次用户访问时携带者Cokkie值,这样服务端就能识别对应的session

缺点

1.服务端保存大量用户状态信息,增大了服务端的压力 2.服务端保存用户状态信息,无法进行水平扩展(用户状态信息只保存在这一个服务器中,访问其他服务器还需要重新登录认证)

简单了说有状态就是服务端需要保存用户状态信息

2.2、什么是无状态

服务端不需要保存任何客户端的用户状态信息 我是刘松林的而

无状态流程

1.用户第一次访问时,服务端要求用户进行身份认证(登录) 2.用户认证成功后,服务端返回一个token,返回给客户端作为令牌 3.当用户再次访问时需要携带该token(令牌),服务端对令牌进行解密判断

优点

1.减少了服务端的压力 2.服务端可以进行任意的伸缩 3.用户登录后可以向多个服务器进行访问而不用进行二次登录

当然无状态最重要的就是保证token的安全性

Ⅲ、jwt的组成

Jwt由三部分组成

1、header

header由两部分组成

1、声明类型:这里是jwt 2、签名算法:比如SHA256、SHA512等

header经过base64编码之后就形成了jwt第一部分

2、Payload(载荷)

payload是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据

iss:发行人 exp:到期时间 sub:主题 …

经过base64编码之后形成了jwt的第二部分

3、Signature(签名)

一般根据前两步的数据,再加上服务的的密钥(secret),通过header里面声明的加密算法生成jwt的第三部分。

secret是服务端用来进行jwt的签发和校验,所以说secret非常重要,任何时候都不应该泄露jwt,一旦jwt泄露,意味着恶意用户可以自我签发jwt了。

三、springboot+springsecurity+mybatis+jwt实现单点登录Ⅰ、核心流程分析

如果使用springsecurity进行过认证操作,那么这下面将会对你来说很简单

1、根据前述的无状态概念,当用户登录成功后,我们需要在服务端进行token的生成(当然这里是使用jwt生成token);然后当用户进行再次访问时,我们需要对token进行解析、判断等;所以我们可以将token的创建、解析、判断等封装到一个工具类JwtTokenUtil中。

2、我们还需要自定义一个AuthenticationFilter过滤器用来拦截请求;判断请求中是否携带token,如果携带了token,那就进行解析判断;如果没有token,则可能是首次登录,所以可以放行,交给springsecurity进行认证(也就是交给UsernamePasswordAuthenticationFilter过滤器)

3、第二步中有一个关键点,我们自定义的AuthenticationFilter过滤器应该加到哪个地方,我们都知道springsecurity本质上是一串过滤器链,将请求进行层层拦截、判断、放行或者不放行。根据第二步的分析我们可以将AuthenticationFilter过滤器加入到UsernamePasswordAuthenticationFilter过滤器前面,这样一来第二步就行很清晰了。

其他就类似于springsecurity进行认证和授权的步骤了

Ⅱ、具体代码实现及其分析1.JwtTokenUtilpackage com.jwt.utils;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jws;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;import java.util.HashMap;import java.util.Map;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/13 * @description: */public class JwtTokenUtils { /** * 请求头 */ private static final String TOKEN_HEADER = “Authorization”; /** * TOKEN 前缀 */ public static final String TOKEN_PREFIX = “Bearer”; /** * 私钥 */ private static final String TOKEN_SECRET = “secret”; /** * 令牌过期时间 : one day */ private static final long TOKEN_EXPIRATION = 1000 * 60 * 60 * 24; /** * 角色权限定义 */ private static final String ROLE_CLAIMS = “role”; /** * 创建令牌 * * @param username * @param role */ public static String createToken(String username, String role) { Map<String, Object> map = new HashMap<>(); map.put(ROLE_CLAIMS, role); return Jwts.builder() .setClaims(map) .setSubject(username) .claim(“username”, username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + TOKEN_EXPIRATION)) .signWith(SignatureAlgorithm.HS256, TOKEN_SECRET) .compact(); } /** * 获取解析后的token信息 * @param token * @return */ public static Claims getTokenBody(String token) { return parseToken(token).getBody(); } /** * 检查token是否存在 * @param token * @return */ public static Claims checkToken(String token) { try { Claims claims = getTokenBody(token); return claims; } catch (Exception e) { return null; } } /** * 判断令牌是否过期 * @param token 令牌 * @return boolean * @describe getExpiration()获取令牌过期时间 */ public static boolean isExpiration(String token) { return getTokenBody(token).getExpiration().before(new Date()); } /** * 解析令牌 * @param token * @return */ public static Jws<Claims> parseToken(String token) { return Jwts.parser().setSigningKey(TOKEN_SECRET).parseClaimsJws(token); } /** * 解析token,获取用户名 */ public static String parseTokenToUsername(String token) { String username = getTokenBody(token).getSubject(); return username; }}2.统一结果返回封装类

使用泛型,因为在实际的场景中可能由多种不同的数据要进行返回

package com.jwt.utils;import lombok.Data;import java.io.Serializable;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/12 * @description: */@Datapublic class ResponseBody<T> implements Serializable { private Integer code; private String msg; private T data; public ResponseBody(Integer code, String msg) { this.code = code; this.msg = msg; } public ResponseBody(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; }}3、handler处理类

这些处理类将在SecurityConfig配置类中进行配置,所以需要先将每个handler处理类放到spring容器中====》@Component注解

3.1、AjaxAccessDeniedHandler权限不足处理类package com.jwt.handler;import com.alibaba.fastjson.JSON;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.web.access.AccessDeniedHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/12 * @description: 权限不足处理类 */@Componentpublic class AjaxAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().write(JSON.toJSONString(“权限不足”)); }}3.2、AjaxAuthenticationEntryPoint匿名无权限处理类

当用户匿名访问资源时(即不登陆去访问资源),会跳转到登录页面

package com.jwt.handler;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/14 * @description: 匿名未认证登录处理类 */@Componentpublic class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setContentType(“application/json;charset=utf-8”); request.getRequestDispatcher(“/login.html”).forward(request,response); }}3.3、AjaxAuthenticationFailureHandler认证失败处理类

当用户登录失败时,会抛出相应的异常,然后交给该类进行判断是哪一种异常。

package com.jwt.handler;import com.alibaba.fastjson.JSON;import lombok.extern.slf4j.Slf4j;import org.springframework.security.authentication.*;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/11 * @description: 认证失败Handler处理类 */@Slf4j //日志@Componentpublic class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler { private String url; public AjaxAuthenticationFailureHandler(){} public AjaxAuthenticationFailureHandler(String url) { this.url = url; } @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String failData = “”; if (exception instanceof AccountExpiredException) { failData = “账号过期”; } else if (exception instanceof UsernameNotFoundException) { failData = “账号不存在”; } else if (exception instanceof CredentialsExpiredException) { failData = “密码过期”; } else if (exception instanceof DisabledException) { failData = “账号不可用”; } else if (exception instanceof LockedException) { failData = “账号锁定”; } else if (exception instanceof BadCredentialsException) { failData = “密码错误”; } else { failData = “未知异常”; } log.info(“认证失败,”+failData); //设置编码格式,否则中文会乱码 response.setCharacterEncoding(“utf-8); response.setContentType(“application/json;charset=utf-8”); //返回统一数据 response.getWriter().write(JSON.toJSONString(failData)); }}3.4、AjaxAuthenticationSuccessHandler认证成功处理类

当用户认证成功后,将交与该处理类处理;返回token(前缀带bearer,所以服务端在进行解析token时,应该将bearer去掉。

package com.jwt.handler;import com.alibaba.fastjson.JSON;import com.jwt.entity.User;import com.jwt.utils.JwtTokenUtils;import com.jwt.utils.ResponseBody;import org.springframework.security.core.Authentication;import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/11 * @description: 认证成功Handler处理类 */@Componentpublic class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private String url; public AjaxAuthenticationSuccessHandler() { } public AjaxAuthenticationSuccessHandler(String url) { this.url = url; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { User user = (User) authentication.getPrincipal(); String token = JwtTokenUtils.createToken(user.getUsername(), String.valueOf(user.getAuthorities())); //设置编码,如果不设置会乱码 response.setCharacterEncoding(“utf-8”); response.setContentType(“application/json;charset=utf-8”); //设置返回的token 带有Bearer的前缀字符串 response.setHeader(“Authorization”, JwtTokenUtils.TOKEN_PREFIX+token); response.getWriter().write(new ResponseBody<User>(200,”登录成功”,user).toString()); }}3.5、AjaxLogoutSuccessHandler退出成功处理类

当用户退出登录成功时,跳转到登录页面

package com.jwt.handler;import lombok.extern.slf4j.Slf4j;import org.springframework.security.core.Authentication;import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/16 * @description: */@Component@Slf4jpublic class AjaxLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { request.getRequestDispatcher(“/login.html”).forward(request,response); }}4、AuthenticationFilter(token的验证类)

该类对请求进行拦截,配置到UsernamePasswordAuthenticationFilter过滤器类的前面,至于功能前面已经分析过。

Spring Security使用一个Authentication对象来描述当前用户的相关信息。SecurityContextHolder中持有的是当前用户的SecurityContext,而SecurityContext持有的是代表当前用户相关信息的Authentication的引用。这个Authentication对象不需要我们自己去创建,在与系统交互的过程中,Spring Security会自动为我们创建相应的Authentication对象,然后赋值给当前的SecurityContext,我们经常会使用SecurityContextHolder获取SecurityContext实例,然后获取Authentication实例。下面会是使用到

package com.jwt.filter;import com.jwt.entity.User;import com.jwt.service.LoginUserDetailService;import com.jwt.utils.JwtTokenUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;import org.springframework.stereotype.Component;import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Componentpublic class AuthenticationFilter extends OncePerRequestFilter { @Autowired LoginUserDetailService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //获取请求头中的token加密串(名字是Authorization,JwtTokenUtils工具类里面定义的 String authHeader = request.getHeader(“Authorization”);//判断是否是以Bearer开头的,这是我自定义的前缀名) if (authHeader != null && authHeader.startsWith(“Bearer “)) { //将Bearer去掉,因为返回token时,我在token前缀加入了Bearer字符串 String authToken = authHeader.substring(“Bearer “.length());//解析加密串,获取用户名 String username = JwtTokenUtils.parseTokenToUsername(authToken); //看当前SecurityContext中是否有Authentication实例(前面已经介绍过) if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {//如果Authentication没有当前用户的信息,然后从数据库中查询出相应的信息 User user = (User) userDetailsService.loadUserByUsername(username); if (user != null) { //封装到UsernamePasswordAuthenticationToken令牌类中 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));//将用户信息放到Authenticatin实例中进行存储 SecurityContextHolder.getContext().setAuthentication(authentication); } } } filterChain.doFilter(request, response); }}

这个过滤器类里面是jwt最核心的点 首先请求进入到该过滤器类时,获取token串,进行解析;然后查看Authentication是否为null,如果为null,根据解析到的用户名去数据库查询相应的信息,一并将其放到Authentication实例中。 当你下一次再进行token判断时,直接从Authentication拿取相应信息即可,不需要再查询数据库。

5、SecurityConfig配置类package com.jwt.config;import com.jwt.filter.AuthenticationFilter;import com.jwt.handler.*;import com.jwt.service.LoginUserDetailService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.AuthenticationProvider;import org.springframework.security.authentication.dao.DaoAuthenticationProvider;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.builders.WebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;import javax.annotation.Resource;import javax.sql.DataSource;@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private LoginUserDetailService userDetailsService; @Autowired private AjaxAccessDeniedHandler deniedHandler; @Autowired private AjaxAuthenticationEntryPoint ajaxAuthenticationEntryPoint; @Autowired private AjaxAuthenticationSuccessHandler successHandler; @Autowired private AjaxAuthenticationFailureHandler failureHandler; @Autowired private DataSource dataSource; @Autowired private AuthenticationFilter authenticationFilter; @Autowired private AjaxLogoutSuccessHandler logoutSuccessHandler; /** * 拦截策略 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().and() .formLogin().loginPage(“/login.html”) .loginProcessingUrl(“/login”) .successHandler(successHandler) .failureHandler(failureHandler) .and() .authorizeRequests().antMatchers(“/login.html”,”/register.html”,”/register”,”/js/**”).permitAll() .and() .exceptionHandling() .accessDeniedHandler(deniedHandler) .authenticationEntryPoint(ajaxAuthenticationEntryPoint) .and().authorizeRequests() .anyRequest().authenticated(); http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class); //设置记住我 http.rememberMe().tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(600) .userDetailsService(userDetailsService); //配置退出登录操作 http.logout().logoutUrl(“/logout”).logoutSuccessHandler(logoutSuccessHandler); //关闭csrf防护 http.csrf().disable(); } @Bean public BCryptPasswordEncoder bCryptPasswordEncoder(){ return new BCryptPasswordEncoder(); } /** * 配置忽略的URL * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers(“/”); } /** * 拦截后需要使用自定义的类和加密解密方式 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(daoAuthenticationProvider()); auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); } @Override @Bean protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } /** * * @return HideUserNotFoundExceptions(false),否则UsernameNotFoundException异常会被BadCredentialsException异常覆盖 */ @Bean public AuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(userDetailsService); daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder()); daoAuthenticationProvider.setHideUserNotFoundExceptions(false); return daoAuthenticationProvider; }//这是spirngsecurity记住我功能需要加入代码//JdbcTokenRepositoryImpl是将其保存到数据库中 @Bean public PersistentTokenRepository persistentTokenRepository() { //这个是记住我保存到数据库的类,当然还有保存到内存的类 JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); //设置数据源 tokenRepository.setDataSource(dataSource); //这个会在第一次使用记住我时在数据库中创建一张表(用户、过期时间等) //在第二次一定要将其注释掉,否者会报错。 // tokenRepository.setCreateTableOnStartup(true); return tokenRepository; }}6、自定义异常处理类6.1、RegisterFailureException注册失败异常处理类package com.jwt.exception;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/14 * @description: */public class RegisterFailureException extends Exception{ private String message; public RegisterFailureException() { super(); } public RegisterFailureException(String message) { this.message = message; }}6.2、RegisterUsernameHasBeenExists注册时用户名已经存在异常处理类package com.jwt.exception;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/15 * @description: */public class RegisterUsernameHasBeenExists extends Exception{ private String message; public RegisterUsernameHasBeenExists(String message) { this.message = message; }}6.3、Controller层全局异常处理类package com.jwt.exception.controllerException;import com.jwt.exception.RegisterUsernameHasBeenExists;import lombok.extern.slf4j.Slf4j;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.http.HttpStatus;import org.springframework.validation.BindException;import org.springframework.validation.BindingResult;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseStatus;import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.sql.SQLException;import java.util.Objects;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/15 * @description: */@Slf4j@RestControllerAdvicepublic class ControllerHandlerExceptionAdvice { private static final Logger logger = LoggerFactory.getLogger(ControllerHandlerExceptionAdvice.class); /** * 拦截表单参数异常处理 * @param exception * @param request * @return */ @ResponseStatus(HttpStatus.OK) @ExceptionHandler({BindException.class}) public String bindException(BindException exception, HttpServletRequest request) { logger.info(“表单拦截校验处理=====>”); logger.info(“表单数据======>”+request.getContentType()); BindingResult bindingResult = exception.getBindingResult(); return Objects.requireNonNull(bindingResult.getFieldError().getDefaultMessage()); } @ExceptionHandler public String handler(HttpServletRequest request, HttpServletResponse response, Exception e) { logger.info(“RestFul 请求发生异常……..”); if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { logger.info(“状态值不是200,正准备修改为200”); response.setStatus(HttpStatus.OK.value()); } if (e instanceof NullPointerException) { logger.error(“发生了空指针异常======》”,e.getMessage()); return “空指针异常”; } else if (e instanceof IllegalArgumentException) { logger.error(“请求参数不匹配异常======>”,e.getMessage()); return “请求参数不匹配”; } else if (e instanceof SQLException) { logger.error(“数据库访问异常======>”,e.getMessage()); return “数据库访问异常”; } else if (e instanceof BindException) { BindingResult bindingResult = ((BindException) e).getBindingResult(); logger.error(“表单校验异常”,bindingResult.getFieldError().getDefaultMessage()); return Objects.requireNonNull(bindingResult.getFieldError().getDefaultMessage()); } else if (e instanceof RegisterUsernameHasBeenExists) { logger.info(“用户名已经存在”); return “用户名已经存在”; } else { logger.error(“未知异常”,e.getMessage()); return “服务器端未知异常,请去检查!”; } }}7、controller,service,mapper,entity,mapper.xml编写

这几个类就是简单的crud了,没什么可说的

7.1、controllerpackage com.jwt.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/7 * @description: */@Controllerpublic class LoginController { @RequestMapping(“/toMain”) public String main() { return “redirect:success.html”; } @RequestMapping(“/”) public String demo() { return “main”; } @RequestMapping(“/logout”) public String logout() { return “redirect:login.html”; }}package com.jwt.controller;import com.jwt.entity.User;import com.jwt.entity.UserDto;import com.jwt.exception.RegisterFailureException;import com.jwt.exception.RegisterUsernameHasBeenExists;import com.jwt.service.RegisterService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.RequestMapping;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/14 * @description: */@Controller@Slf4jpublic class RegisterController { @Autowired private RegisterService registerService; @RequestMapping(“/register”) public String register(@Validated UserDto userDto) throws RegisterUsernameHasBeenExists { log.info(“注册的数据为:”+userDto.toString() ); User user = registerService.selectDup(userDto.getUsername()); if (user == null) { throw new RegisterUsernameHasBeenExists(“用户已存在”); } try { registerService.register(userDto); } catch (RegisterFailureException e) { e.printStackTrace(); } return “/”; }}package com.jwt.controller;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.web.bind.annotation.*;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/15 * @description: */@RestController@RequestMapping(“/api”)@EnableWebSecuritypublic class TestController { @RequestMapping(“/add”) public String add() { return “add”; } @RequestMapping(“select”) public String select() { return “select”; } @RequestMapping(“/delete”) public String delete() { return “delete”; } @RequestMapping(“/update”) public String update() { return “update”; }}7.2、servicepackage com.jwt.service;import com.jwt.entity.User;import com.jwt.entity.UserDto;import com.jwt.exception.RegisterFailureException;import com.jwt.exception.RegisterUsernameHasBeenExists;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/14 * @description: */public interface RegisterService { boolean register(UserDto userDto) throws RegisterFailureException; User selectDup(String username) throws RegisterUsernameHasBeenExists;}

这个LoginUserDetailService是要实现UserDetailService,根据用户名查询数据数据库用户信息,将其交给SpringSecurity进行认证

至于为什么要这样:

package com.jwt.service;import com.jwt.entity.User;import com.jwt.mapper.LoginMapper;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/7 * @description: 根据传来的username去数据库中查询用户信息,然后交给springsecurity去认证 */@Service@Slf4jpublic class LoginUserDetailService implements UserDetailsService { @Autowired private LoginMapper loginMapper; @Autowired private PasswordEncoder passwordEncoder; /** * * 自定义认证 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (username == null || username.equals(“”)) { throw new RuntimeException(“用户名不能为空!”); } User user = loginMapper.findByUsername(username); if (user == null) { throw new UsernameNotFoundException(“用户不能为空!”); } log.info(“根据username查询的用户信息为===>”+user.toString() + “==》等待认证!”); List<GrantedAuthority> authorities = new ArrayList<>();// loginMapper.findAuthorityByUsername(username).forEach(role->// authorities.add(new SimpleGrantedAuthority((String) role))// ); authorities.add(new SimpleGrantedAuthority(loginMapper.findAuthorityByUsername(username))); user.setAuthorities(authorities); log.info(user.getUsername()+”用户的所有权限为权限===>”+authorities.toString()); return new User(user.getUsername(), passwordEncoder.encode(user.getPassword()),authorities); }}package com.jwt.service.impl;import com.jwt.entity.User;import com.jwt.entity.UserDto;import com.jwt.exception.RegisterFailureException;import com.jwt.exception.RegisterUsernameHasBeenExists;import com.jwt.mapper.RegisterMapper;import com.jwt.service.RegisterService;import com.sun.deploy.association.RegisterFailedException;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/14 * @description: */@Service@Slf4jpublic class RegisterServiceImpl implements RegisterService { @Autowired private RegisterMapper registerMapper; @Override public boolean register(UserDto userDto) throws RegisterFailureException { boolean b = registerMapper.register(userDto); System.out.println(b); if (!b) { log.info(“注册失败”); throw new RegisterFailureException(“注册失败”); } return b; } @Override public User selectDup(String username) throws RegisterUsernameHasBeenExists { User user = registerMapper.selectDup(username); if (user != null) { throw new RegisterUsernameHasBeenExists(“用户名已经存在”); } return null; }}7.4、mapper@Repositorypublic interface LoginMapper { User findByUsername(String username); String findAuthorityByUsername(String username);}@Repositorypublic interface RegisterMapper { boolean register(UserDto userDto); User selectDup(String username);}7.5、mapper.xml<?xml version=”1.0″ encoding=”UTF-8″ ?><!DOCTYPE mapper PUBLIC “-//mybatis.org//DTD Mapper 3.0//EN” “http://mybatis.org/dtd/mybatis-3-mapper.dtd”><mapper namespace=”com.jwt.mapper.LoginMapper”> <select id=”findByUsername” resultType=”User” parameterType=”string”> select * from user where username = #{username} limit 1 </select> <select id=”findAuthorityByUsername” resultType=”string”> select authority from role where username = #{username} limit 1 </select></mapper><?xml version=”1.0″ encoding=”UTF-8″ ?><!DOCTYPE mapper PUBLIC “-//mybatis.org//DTD Mapper 3.0//EN” “http://mybatis.org/dtd/mybatis-3-mapper.dtd”><mapper namespace=”com.jwt.mapper.RegisterMapper”> <insert id=”register” parameterType=”userDto”> insert into user (username,password) values(#{username},#{password}) </insert> <select id=”selectDup” resultType=”user”> select * from user where username=#{username} limit 1 </select></mapper>7.6、entity@Data@NoArgsConstructorpublic class User implements UserDetails { private String username; private String password; private List<GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } public User(String username, String password, List<GrantedAuthority> authorities) { this.username = username; this.password = password; this.authorities = authorities; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }}@Datapublic class Authority implements Serializable { private String roles;}package com.jwt.entity;import com.sun.istack.internal.NotNull;import lombok.Data;import javax.validation.constraints.Size;import java.io.Serializable;/** * @author:抱着鱼睡觉的喵喵 * @date:2021/4/14 * @description: */@Datapublic class UserDto implements Serializable { @NotNull @Size(min = 3, max = 15, message = “用户名必须在3~15之间”) private String username; @NotNull @Size(min = 5, max = 15, message = “密码必须在5~15之间”) private String password;}8、application.ymlspring: datasource: url: jdbc:mysql://localhost:3306/security?serverTimezone=UTC username: root password: hao20001010mybatis: mapper-locations: mapper/** type-aliases-package: com.jwt.entityserver: port: 80819、html测试

templates模板下的main.html

<!DOCTYPE html><html lang=”en” xmlns=”http://www.w3.org/1999/xhtml” ><head> <meta charset=”UTF-8″> <title>Title</title></head><body> <h1>这是主页面</h1> <br/> <a href=”login.html”>登录</a> <a href=”register.html”>注册</a> <br> <hr> <h2>操作如下</h2> <a href=”/api/add”>添加商品</a> <a href=”/api/select”>查询商品</a> <a href=”/api/update”>修改商品</a> <a href=”/api/delete”>删除商品</a> <a href=”/logout”>退出登录</a></body></html>

static文件下

<!DOCTYPE html><html lang=”en”><head> <meta charset=”UTF-8″> <title>Title</title></head><body><h2>=======登录页面=========</h2> <form action=”/login” method=”post”> username:<input type=”text” name=”username”/><br/> password:<input type=”password” name=”password” /><br/> remember me:<input type=”checkbox” name=”remember-me” value=”true”/><br/> <input type=”submit” value=”提交” /> </form></body></html><!DOCTYPE html><html lang=”en”><head> <meta charset=”UTF-8″> <title>Title</title></head><body><h2>=========注册页面=========</h2><form action=”/register” method=”post”> <table> username:<input type=”text” name=”username”/><br/> password:<input type=”password” name=”password”/><br/> <input type=”submit” value=”提交”/> </table></form></body></html><!DOCTYPE html><html lang=”en”><head> <meta charset=”UTF-8″> <title>Title</title></head><body><h1>分页面</h1><br><a href=”/toMain”>主页面</a><br><a href=”/logout”>退出</a></body></html>

到这里也就是结束了,测试截图就不再发了,Controller层应该使用Rest风格的。

回顾这个小demo,其实简单了分析就是:springsecurity是一串过滤器链,然后自定义jwt的校验token过滤器,最后将其加入到过滤器链中,至于加到什么位置,想必都明白了。

核心就前端那一段,其他的基本数就是springsecurity的基本操作和一些crud操作及其某些细节。把握住核心即可。

别人失去了信心,他却下决心实现自己的目标。

springboot+springsecurity+mybatis+jwt实现单点登录(详细

相关文章:

你感兴趣的文章:

标签云: