您现在的位置是:首页 >其他 >Spring Security + Jwt 集成实现登录网站首页其他
Spring Security + Jwt 集成实现登录
简介Spring Security + Jwt 集成实现登录
文章目录
- 前言
- Maven 相关依赖
- 配置文件
- 自定义springsecurity相关认证流程
- 继承WebSecurityConfigurerAdapter
- 继承AbstractAuthenticationToken
- 继承AbstractAuthenticationProcessingFilter
- 实现AuthenticationProvider
- 实现UserDetailsService
- 实现AccessDeniedHandler
- 实现AuthenticationEntryPoint
- 实现AuthenticationFailureHandler
- 实现AuthenticationFailureHandler
- 实现AuthenticationSuccessHandler
- 实现AuthenticationSuccessHandler
- 继承OncePerRequestFilter
- 总结
前言
主要实现了如下登录方式:
- 用户名+密码+验证码登录
- 邮箱+验证码登录
Maven 相关依赖
主要引入了如下依赖:
- spring-boot-starter-security依赖
- 为了解决警告排除了kotlin相关的三个依赖包
- jjwt依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<!--解决警告: For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath-->
<exclusions>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</exclusion>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</exclusion>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
配置文件
主要配置如下:
- 配置了jwt生成token秘钥
- 配置了jwt的token过期时间,单位为毫秒
- 配置了header中携带token的key值
jwt: #jwt参数配置
secret: secret
expiration: 14400000
authorization: Authorization
自定义springsecurity相关认证流程
继承WebSecurityConfigurerAdapter
主要做了如下操作:
- 重写了安全框架全局配置
configureGlobal(AuthenticationManagerBuilder auth)
方法
- 指定了密码加密规则
- 关闭了隐藏用户找不到异常功能
- 放开了http防火墙,不对非法json请求拦截
- 重写了http安全配置
configure(HttpSecurity http)
方法
- 禁用csr攻击保护
- 禁用form表单登录
- 禁用页面缓存
- 自定义异常拦截处理
- 身份验证失败
MyAuthenticationEntryPoint()
异常拦截器- 访问被拒绝
MyAccessDeniedHandler()
异常拦截器- 会话创建策略无状态(不使用session)
- 授权请求配置
- 接口请求
UsernamePasswordAuthenticationFilter()
拦截器之前添加令牌验证过滤器- 登出成功
MyLogoutSuccessHandler()
拦截器- 添加自定义身份验证适配器
MyAuthenticationConfigurer()
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private MyAccessDeniedHandler accessDeniedHandler;
@Autowired
private MyLogoutSuccessHandler logoutSuccessHandler;
@Autowired
private JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter;
/**
* 全局配置
*
* @param auth 身份验证管理器生成器
*/
@SneakyThrows
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
//自定义service
daoAuthenticationProvider.setUserDetailsService(super.userDetailsService());
//配置密码生成规则
daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
//不隐藏异常
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
auth.authenticationProvider(daoAuthenticationProvider);
}
/**
* http防火墙,放开非法json请求
*
* @return HttpFirewall
*/
@Bean
public HttpFirewall httpFirewall() {
return new DefaultHttpFirewall();
}
/**
* http安全配置
*
* @param http the {@link HttpSecurity} to modify
*/
@SneakyThrows
@Override
protected void configure(HttpSecurity http) {
http
//禁用csr攻击保护
.csrf().disable()
// 禁用form表单登录
.formLogin().disable()
// 禁用页面缓存
.headers().cacheControl().disable().and()
//异常处理
.exceptionHandling()
//身份验证失败
.authenticationEntryPoint(authenticationEntryPoint)
//访问被拒绝的处理程序
.accessDeniedHandler(accessDeniedHandler)
.and()
//会话创建策略无状态(不使用session)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//授权请求
.authorizeRequests()
//允许所有
.antMatchers(HttpMethod.OPTIONS, HttpPermitAll.getHttpOptionPermits()).permitAll()
.antMatchers(HttpMethod.POST, HttpPermitAll.getHttpPostPermits()).permitAll()
.antMatchers(HttpMethod.PUT, HttpPermitAll.getHttpPutPermits()).permitAll()
.antMatchers(HttpMethod.GET, HttpPermitAll.getHttpGetPermits()).permitAll()
.antMatchers(WebPermitAll.getWebPermits()).permitAll()
//除了上面配置的请求白名单,任何请求都需要验证
.anyRequest().authenticated()
.and()
//添加令牌验证过滤器
.addFilterAfter(jwtAuthorizationTokenFilter, UsernamePasswordAuthenticationFilter.class)
//登出成功过滤器
.logout().logoutSuccessHandler(logoutSuccessHandler)
//添加自定义身份验证适配器
.and().apply(new MyAuthenticationConfigurer());
}
}
继承AbstractAuthenticationToken
主要做了如下操作:
- 重新定义了principal概念
- 增加了验证码字段code
- 增加了验证码key字段uuid
- 登录类型type
- 自定义带参的构造函数
- 新增的参数对应的Getter和Setter方法
public class MyAuthenticationToken extends AbstractAuthenticationToken {
/**
* 用户名/邮箱
*/
private final Object principal;
/**
* 密码
*/
private Object credentials;
/**
* 验证码
*/
private String code;
/**
* 验证码id
*/
private String uuid;
/**
* 登录类型
*/
private String type;
public MyAuthenticationToken(Object principal,
Object credentials,
String code,
String uuid,
String type) {
super(null);
this.principal = principal;
this.credentials = credentials;
this.code = code;
this.uuid = uuid;
this.type = type;
setAuthenticated(false);
}
public MyAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities, Object credentials) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getUuid() {
return uuid;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
credentials = null;
}
}
继承AbstractAuthenticationProcessingFilter
主要做了如下操作:
- 从HttpServletRequest获取到相关参数
- 整理验证参数实体Authentication并移交给验证器AuthenticationManager
public class MyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public MyAuthenticationFilter(RequestMatcher matcher, AuthenticationManager localAuthManager) {
super(matcher, localAuthManager);
}
@SneakyThrows
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter(Constant.USERNAME);
String password = request.getParameter(Constant.PASSWORD);
String code = request.getParameter(Constant.CODE);
String uuid = request.getParameter(Constant.UUID);
String type = request.getParameter(Constant.TYPE);
//整理验证信息并移交给验证器
Authentication authentication = new MyAuthenticationToken(username, password, code, uuid, type);
return this.getAuthenticationManager().authenticate(authentication);
}
}
实现AuthenticationProvider
主要做了如下操作:
- 实现了认证方法
authenticate(Authentication authentication)
- 通过Authentication获取登录相关信息
- 验证登录类型是否为空
- 如果是用户名密码验证码登录则执行
defaultLogin()
- 如果是邮箱验证码登录则执行
emailLogin()
函数- 调用
loadUserByUsername()
函数,校验用户是否有效- 整理用户信息,回填验证信息到上下文中
- 创建了用户名密码验证码登录私有化函数
defaultLogin()
- 通过uuid获取缓存中验证码
- 验证输入uuid和输入的验证码是否为空
- 验证缓存中是否存在uuid对应的验证码
- 验证输入的验证码和缓存中的验证码是否一致
- 通过用户名称获取用户信息
- 验证用户是否存在
- 验证密码是否正确
- 返回用户名称或者对应的错误代码
- 创建了邮箱-验证码登录私有化函数
emailLogin()
- 通过uuid获取缓存中验证码
- 验证输入uuid和输入的验证码是否为空
- 验证缓存中是否存在uuid对应的验证码
- 验证输入的验证码和缓存中的验证码是否一致
- 通过邮箱获取用户信息
- 验证用户是否存在
- 返回用户名称或者对应的错误代码
- 实现了
supports(Class<?> authentication)
方法
- 指定自定义身份验证方法
MyAuthenticationProvider
使用自定义的MyAuthenticationToken
实体类
public class MyAuthenticationProvider implements AuthenticationProvider {
private final RedisUtil redisUtil;
private final IUserService userService;
private final IUserInfoService userInfoService;
public MyAuthenticationProvider(RedisUtil redisUtil,
IUserService userService,
IUserInfoService userInfoService) {
this.redisUtil = redisUtil;
this.userService = userService;
this.userInfoService = userInfoService;
}
@SneakyThrows
@Override
public Authentication authenticate(Authentication authentication) {
MyAuthenticationToken authenticationToken = (MyAuthenticationToken) authentication;
String username = (String) authenticationToken.getPrincipal();
String credentials = (String) authenticationToken.getCredentials();
String code = authenticationToken.getCode();
String uuid = authenticationToken.getUuid();
String type = authenticationToken.getType();
//验证类型为空
if (StringUtils.isBlank(type)){
username = MyCode.AUTHENTICATION_TYPE_EMPTY;
}
//用户名密码验证码登录
if (type.equals(AuthTypeEnum.USERNAME_PASSWORD.getCode())) {
username = defaultLogin(username, credentials, code, uuid);
}else
//邮箱验证码登录
if (type.equals(AuthTypeEnum.EMAIL_CODE.getCode())) {
username = emailLogin(username, code, uuid);
}else {
//验证类型无效
username = MyCode.AUTHENTICATION_TYPE_INVALID;
}
//校验用户并返回用户信息
UserDetails userDetails = userService.loadUserByUsername(username);
//回填验证信息到上下文中
MyAuthenticationToken myAuthenticationToken =
new MyAuthenticationToken(userDetails,
userDetails.getAuthorities(), authenticationToken.getCredentials());
myAuthenticationToken.setDetails(authenticationToken);
return myAuthenticationToken;
}
/**
* 用户名密码验证码登录
* @param username 用户名
* @param credentials 密码
* @param code 验证码
* @param uuid 随机数
* @return 用户名称或者状态码
*/
private String defaultLogin(String username, String credentials, String code, String uuid) {
String accountCode = redisUtil.get(RedisConstant.USER_VERIFICATION_CODE.concat(CommonConstant.COLON).concat(uuid));
if (StringUtils.isBlank(code) || StringUtils.isBlank(uuid)) {
username = MyCode.VERIFICATION_CODE_EMPTY;
}else if (accountCode == null){
username = MyCode.VERIFICATION_CODE_FAIL;
}else if (!Objects.equals(accountCode, code)) {
username = MyCode.VERIFICATION_CODE_ERROR;
}else {
QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();
userInfoQueryWrapper.eq(UserInfo.USERNAME, username);
UserInfo userInfo = userInfoService.getOne(userInfoQueryWrapper);
if (userInfo == null){
username = MyCode.USER_NOT_FOUND;
}else if (!new BCryptPasswordEncoder().matches(credentials, userInfo.getPwd())) {
username = MyCode.PASSWORD_ERROR;
}else {
username = userInfo.getUsername();
}
}
return username;
}
/**
* 邮箱验证码登录
* @param username 邮箱
* @param code 验证码
* @param uuid 随机数
* @return 用户名称或者状态码
*/
private String emailLogin(String username, String code, String uuid) {
String accountCode = redisUtil.get(RedisConstant.EMAIL_VERIFICATION_CODE.concat(CommonConstant.COLON).concat(uuid));
if (StringUtils.isBlank(code) || StringUtils.isBlank(uuid)) {
username = MyCode.VERIFICATION_CODE_EMPTY;
}else if (accountCode == null){
username = MyCode.VERIFICATION_CODE_FAIL;
} else if (!accountCode.equals(code)) {
username = MyCode.VERIFICATION_CODE_ERROR;
}else {
QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();
userInfoQueryWrapper.eq(UserInfo.EMAIL, username);
UserInfo userInfo = userInfoService.getOne(userInfoQueryWrapper);
if (userInfo == null){
username = MyCode.USER_NOT_FOUND;
}else {
username = userInfo.getUsername();
}
}
return username;
}
@Override
public boolean supports(Class<?> authentication) {
return MyAuthenticationToken.class.isAssignableFrom(authentication);
}
}
实现UserDetailsService
主要做了如下操作:
- 实现了
loadUserByUsername(String username)
方法
- 对输入的username(错误代码或者是用户名)进行匹配
- 验证码为空
- 验证码失效
- 验证码错误
- 用户不存在
- 密码错误
- 获取缓存中的用户信息
- 校验缓存中是否存在用户信息
- 不存在则通过用户名称获取用户信息
- 封装为
LoginUserVo()
实体- 验证用户名密码错误
- 验证用户过期
- 验证用户锁定
- 验证用户凭证过期
- 验证用户禁用
- 返回用户信息
UserDetails()
@Service
public class UserServiceImpl implements IUserService, UserDetailsService {
@Autowired
private RedisUtil redisUtil;
@Autowired
private IUserInfoService userInfoService;
@Override
public UserDetails loadUserByUsername(String username) {
// 验证码为空
if (username.equals(MyCode.VERIFICATION_CODE_EMPTY)){
throw new UsernameNotFoundException(
I18nUtils.getMessage(LoginConstant.VERIFICATION_CODE_EMPTY));
}
// 验证码失效
if (username.equals(MyCode.VERIFICATION_CODE_FAIL)){
throw new UsernameNotFoundException(
I18nUtils.getMessage(LoginConstant.VERIFICATION_CODE_FAIL));
}
// 验证码错误
if (username.equals(MyCode.VERIFICATION_CODE_ERROR)){
throw new UsernameNotFoundException(
I18nUtils.getMessage(LoginConstant.VERIFICATION_CODE_ERROR));
}
// 用户不存在
if (username.equals(MyCode.USER_NOT_FOUND)){
throw new UsernameNotFoundException(
I18nUtils.getMessage(LoginConstant.BADCREDENTIALS_EXCEPTION));
}
// 密码错误
if (username.equals(MyCode.PASSWORD_ERROR)){
throw new UsernameNotFoundException(
I18nUtils.getMessage(LoginConstant.PASSWORD_ERROR));
}
String userInfoStr = redisUtil.get(RedisConstant.USER.concat(CommonConstant.COLON).concat(username));
LoginUserVo loginUserVo = null;
if (userInfoStr != null) {
loginUserVo = JSON.parseObject(userInfoStr, LoginUserVo.class);
}else {
QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();
userInfoQueryWrapper.eq(UserInfo.USERNAME, username);
UserInfo userInfo = userInfoService.getOne(userInfoQueryWrapper);
if (userInfo != null) {
loginUserVo = new LoginUserVo();
BeanUtils.copyProperties(userInfo, loginUserVo);
}
}
//用户名或密码错误
if (loginUserVo == null) {
throw new UsernameNotFoundException(
I18nUtils.getMessage(LoginConstant.BADCREDENTIALS_EXCEPTION));
}
//用户过期
if (loginUserVo.getIsExpired()) {
throw new AccountExpiredException(
I18nUtils.getMessage(LoginConstant.ACCOUNTEXPIRED_EXCEPTION));
}
//用户锁定
if (loginUserVo.getIsLocked()) {
throw new LockedException(
I18nUtils.getMessage(LoginConstant.LOCKED_EXCEPTION));
}
//用户凭证过期
if (loginUserVo.getIsCredentialsExpired()) {
throw new CredentialsExpiredException(
I18nUtils.getMessage(LoginConstant.CREDENTIALSEXPIRED_EXCEPTION));
}
//用户已禁用过期
if (loginUserVo.getIsDisable()) {
throw new DisabledException(
I18nUtils.getMessage(LoginConstant.DISABLED_EXCEPTION));
}
return loginUserVo;
}
}
实现AccessDeniedHandler
主要做了如下操作:
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@SneakyThrows
@Override
public void handle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, AccessDeniedException e) {
httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
PrintWriter out = httpServletResponse.getWriter();
out.write(JSON.toJSONString(
RespJson.error(HttpServletResponse.SC_FORBIDDEN,
I18nUtils.getMessage(LoginConstant.FORBIDDEN))));
}
}
实现AuthenticationEntryPoint
主要做了如下操作:
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@SneakyThrows
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, AuthenticationException e) {
httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter out = httpServletResponse.getWriter();
out.write(JSON.toJSONString(
RespJson.error(HttpServletResponse.SC_UNAUTHORIZED,
I18nUtils.getMessage(LoginConstant.UNAUTHORIZED))));
}
}
实现AuthenticationFailureHandler
主要做了如下操作:
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@SneakyThrows
@Override
public void onAuthenticationFailure(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, AuthenticationException e) {
httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);
httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
PrintWriter out = httpServletResponse.getWriter();
if (e instanceof BadCredentialsException) {
out.write(JSON.toJSONString(
RespJson.error(I18nUtils.getMessage(LoginConstant.PASSWORD_ERROR))));
} else {
out.write(JSON.toJSONString(RespJson.error(e.getMessage())));
}
}
}
实现AuthenticationFailureHandler
主要做了如下操作:
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@SneakyThrows
@Override
public void onAuthenticationFailure(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, AuthenticationException e) {
httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);
httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
PrintWriter out = httpServletResponse.getWriter();
if (e instanceof BadCredentialsException) {
out.write(JSON.toJSONString(
RespJson.error(I18nUtils.getMessage(LoginConstant.PASSWORD_ERROR))));
} else {
out.write(JSON.toJSONString(RespJson.error(e.getMessage())));
}
}
}
实现AuthenticationSuccessHandler
主要做了如下操作:
- 根据上下文中用户信息生成token
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final IUserService userService;
public MyAuthenticationSuccessHandler(IUserService userService) {
this.userService = userService;
}
@SneakyThrows
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) {
String token = userService.getToken((LoginUserVo) authentication.getPrincipal());
httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
PrintWriter out = httpServletResponse.getWriter();
out.write(JSON.toJSONString(
RespJson.success(I18nUtils.getMessage(LoginConstant.LOGIN_SUCCESS), token)));
}
}
实现AuthenticationSuccessHandler
主要做了如下操作:
- HttpServletRequest中获取token
- 解析token拿到用户名称
- 根据用户名称删除缓存中的用户信息
- 根据用户名称删除缓存中的权限信息
- 根据用户名称删除缓存中的用户获取token剩余过期时间
- 将未过期token加入到黑名单中,过期时间为剩余过期时间
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Autowired
private RedisUtil redisUtil;
@Value("${jwt.authorization}")
private String authorization;
@Autowired
private JwtTokenUtils jwtTokenUtils;
@SneakyThrows
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) {
//获取token
String token = httpServletRequest.getHeader(authorization);
//获取token中的信息
Claims allClaimsFromToken = jwtTokenUtils.getAllClaimsFromToken(token);
//删除redis中用户信息
redisUtil.delete(RedisConstant.USER
.concat(CommonConstant.COLON)
.concat(allClaimsFromToken.getSubject()));
//删除redis中权限信息
redisUtil.delete(RedisConstant.URL
.concat(CommonConstant.COLON)
.concat(allClaimsFromToken.getSubject()));
//获取当前token剩余毫秒数
long expiration = jwtTokenUtils.getExpirationTimeMillisFromToken(token);
//添加token黑名单
redisUtil.setEx(
RedisConstant.EXPIRATION_TOKEN
.concat(CommonConstant.COLON)
.concat(token),
JSON.toJSONString(allClaimsFromToken),
expiration,
TimeUnit.MILLISECONDS);
//返回提示
httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);
PrintWriter out = httpServletResponse.getWriter();
out.write(JSON.toJSONString(
RespJson.success(I18nUtils.getMessage(LoginConstant.LOGOUT_SUCCESS))));
}
}
继承OncePerRequestFilter
主要做了如下操作:
- 通过HttpServletRequest获取token
- 验证token是否为空
- 解析token获取用户名称
- 验证用户名称是否为空
- 验证上下文中认证信息是否存在
- 验证token是否有效
- 验证token是否在黑名单中
- 调用
loadUserByUsername()
函数,校验用户是否有效- 整理用户信息,回填验证信息到上下文中
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtils jwtTokenUtils;
@Value("${jwt.authorization}")
private String authorization;
@Autowired
private IUserService userService;
@Autowired
private RedisUtil redisUtil;
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain chain) {
//获取请求携带的token
final String token = request.getHeader(authorization);
//验证token是否为空
if (StringUtils.isNotBlank(token)) {
//解析token中的用户名
String username = jwtTokenUtils.getUsernameFromToken(token);
//验证用户名称是否为空
//验证上下文中认证信息是否存在
//验证token是否有效
//验证token是否在黑名单中
if (StringUtils.isNotBlank(username) &&
SecurityContextHolder.getContext().getAuthentication() == null &&
jwtTokenUtils.validateToken(token, username) &&
!redisUtil.hasKey(RedisConstant.EXPIRATION_TOKEN.concat(CommonConstant.COLON).concat(token))) {
//校验用户并返回用户信息
UserDetails userDetails = userService.loadUserByUsername(username);
//回填验证信息到上下文中
MyAuthenticationToken authentication = new MyAuthenticationToken(
userDetails, userDetails.getAuthorities(), token);
authentication.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
//继续执行
chain.doFilter(request, response);
}
}
总结
我们为了实现多种登陆方式,重写了springsecurity的认证拦截器中的方法Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
,包装了带有自定义参数的AbstractAuthenticationToken.java
,自定义了身份验证接口AuthenticationProvider()
中的Authentication authenticate(Authentication authentication)
方法,完成了多种登录方式的整合。
至此,完成了springboot+springsecurity+jwt的整合。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。