项目下载:https://download.csdn.net/download/Kevinnsm/16751962
文章目录
Ⅰ、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.JwtTokenUtil
package 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:
*/
@Data
public 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: 权限不足处理类
*/
@Component
public 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: 匿名未认证登录处理类
*/
@Component
public 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 //日志
@Component
public 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处理类
*/
@Component
public 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
@Slf4j
public 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;
@Component
public 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;
@Configuration
public 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
@RestControllerAdvice
public 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、controller
package 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:
*/
@Controller
public 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
@Slf4j
public 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")
@EnableWebSecurity
public 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、service
package 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进行认证
至于为什么要这样:https://blog.csdn.net/Kevinnsm/article/details/115189976
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
@Slf4j
public 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
@Slf4j
public 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
@Repository
public interface LoginMapper {
User findByUsername(String username);
String findAuthorityByUsername(String username);
}
@Repository
public 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
@NoArgsConstructor
public 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;
}
}
@Data
public 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:
*/
@Data
public 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.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/security?serverTimezone=UTC
username: root
password: hao20001010
mybatis:
mapper-locations: mapper/**
type-aliases-package: com.jwt.entity
server:
port: 8081
9、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操作及其某些细节。把握住核心即可。