修复token过长无法登陆

This commit is contained in:
LionCity
2020-08-13 23:40:42 +08:00
parent 62481b9f31
commit 278e3c560f
13 changed files with 331 additions and 80 deletions

View File

@ -54,19 +54,24 @@
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.jsonwebtoken</groupId>-->
<!-- <artifactId>jjwt-api</artifactId>-->
<!-- <version>${jjwt.version}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.jsonwebtoken</groupId>-->
<!-- <artifactId>jjwt-impl</artifactId>-->
<!-- <version>${jjwt.version}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.jsonwebtoken</groupId>-->
<!-- <artifactId>jjwt-jackson</artifactId>-->
<!-- <version>${jjwt.version}</version>-->
<!-- </dependency>-->
<!-- quartz -->
<dependency>

View File

@ -9,7 +9,7 @@ import co.yixiang.annotation.AnonymousAccess;
import co.yixiang.modules.security.security.JwtAccessDeniedHandler;
import co.yixiang.modules.security.security.JwtAuthenticationEntryPoint;
import co.yixiang.modules.security.security.TokenConfigurer;
import co.yixiang.modules.security.security.TokenProvider;
import co.yixiang.modules.security.security.TokenUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -40,14 +40,14 @@ import java.util.Set;
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final TokenUtil tokenUtil;
private final CorsFilter corsFilter;
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
public SecurityConfig(TokenProvider tokenProvider, CorsFilter corsFilter, JwtAuthenticationEntryPoint authenticationErrorHandler, JwtAccessDeniedHandler jwtAccessDeniedHandler, ApplicationContext applicationContext) {
this.tokenProvider = tokenProvider;
public SecurityConfig(TokenUtil tokenUtil, CorsFilter corsFilter, JwtAuthenticationEntryPoint authenticationErrorHandler, JwtAccessDeniedHandler jwtAccessDeniedHandler, ApplicationContext applicationContext) {
this.tokenUtil = tokenUtil;
this.corsFilter = corsFilter;
this.authenticationErrorHandler = authenticationErrorHandler;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
@ -133,6 +133,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider);
return new TokenConfigurer(tokenUtil);
}
}

View File

@ -27,7 +27,7 @@ public class SecurityProperties {
/** 必须使用最少88位的Base64对该令牌进行编码 */
private String base64Secret;
private String secret;
/** 令牌过期时间 此处单位/毫秒 */
private Long tokenValidityInSeconds;

View File

@ -11,7 +11,7 @@ import co.yixiang.annotation.AnonymousAccess;
import co.yixiang.exception.BadRequestException;
import co.yixiang.logging.aop.log.Log;
import co.yixiang.modules.security.config.SecurityProperties;
import co.yixiang.modules.security.security.TokenProvider;
import co.yixiang.modules.security.security.TokenUtil;
import co.yixiang.modules.security.security.vo.AuthUser;
import co.yixiang.modules.security.security.vo.JwtUser;
import co.yixiang.modules.security.service.OnlineUserService;
@ -29,14 +29,10 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
@ -64,15 +60,15 @@ public class AuthController {
private final RedisUtils redisUtils;
private final UserDetailsService userDetailsService;
private final OnlineUserService onlineUserService;
private final TokenProvider tokenProvider;
private final TokenUtil tokenUtil;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthController(SecurityProperties properties, RedisUtils redisUtils, UserDetailsService userDetailsService, OnlineUserService onlineUserService, TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
public AuthController(SecurityProperties properties, RedisUtils redisUtils, UserDetailsService userDetailsService, OnlineUserService onlineUserService, TokenUtil tokenUtil, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.properties = properties;
this.redisUtils = redisUtils;
this.userDetailsService = userDetailsService;
this.onlineUserService = onlineUserService;
this.tokenProvider = tokenProvider;
this.tokenUtil = tokenUtil;
this.authenticationManagerBuilder = authenticationManagerBuilder;
}
@ -100,7 +96,8 @@ public class AuthController {
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成令牌
String token = tokenProvider.createToken(authentication);
final UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String token = tokenUtil.generateToken(userDetails);
final JwtUser jwtUser = (JwtUser) authentication.getPrincipal();
// 保存在线信息
onlineUserService.save(jwtUser, token, request);
@ -153,7 +150,7 @@ public class AuthController {
@AnonymousAccess
@DeleteMapping(value = "/logout")
public ResponseEntity<Object> logout(HttpServletRequest request){
onlineUserService.logout(tokenProvider.getToken(request));
onlineUserService.logout(tokenUtil.getToken(request));
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@ -15,15 +15,15 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
*/
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
private final TokenUtil tokenUtil;
public TokenConfigurer(TokenProvider tokenProvider){
this.tokenProvider = tokenProvider;
public TokenConfigurer(TokenUtil tokenUtil){
this.tokenUtil = tokenUtil;
}
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenProvider);
TokenFilter customFilter = new TokenFilter(tokenUtil);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}

View File

@ -1,7 +1,6 @@
/**
* Copyright (C) 2018-2020
* All rights reserved, Designed By www.yixiang.co
*/
package co.yixiang.modules.security.security;
@ -9,11 +8,13 @@ import co.yixiang.modules.security.config.SecurityProperties;
import co.yixiang.modules.security.service.OnlineUserService;
import co.yixiang.modules.user.vo.OnlineUser;
import co.yixiang.utils.SpringContextHolder;
import co.yixiang.utils.StringUtils;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
@ -29,43 +30,46 @@ import java.io.IOException;
@Slf4j
public class TokenFilter extends GenericFilterBean {
private final TokenProvider tokenProvider;
private final TokenUtil tokenUtil;
TokenFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
TokenFilter(TokenUtil tokenUtil) {
this.tokenUtil = tokenUtil;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = resolveToken(httpServletRequest);
String requestRri = httpServletRequest.getRequestURI();
// 验证 token 是否存在
OnlineUser onlineUser = null;
try {
SecurityProperties properties = SpringContextHolder.getBean(SecurityProperties.class);
OnlineUserService onlineUserService = SpringContextHolder.getBean(OnlineUserService.class);
onlineUser = onlineUserService.getOne(properties.getOnlineKey() + token);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
}
if (onlineUser != null && StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestRri);
} else {
log.debug("no valid JWT token found, uri: {}", requestRri);
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
private String resolveToken(HttpServletRequest request) {
SecurityProperties properties = SpringContextHolder.getBean(SecurityProperties.class);
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
return bearerToken.substring(7);
}
return null;
}
String requestRri = httpServletRequest.getRequestURI();
// 验证 token 是否存在
OnlineUser onlineUser = null;
String authToken = "";
try {
SecurityProperties properties = SpringContextHolder.getBean(SecurityProperties.class);
OnlineUserService onlineUserService = SpringContextHolder.getBean(OnlineUserService.class);
authToken = tokenUtil.getToken(httpServletRequest);
if (authToken == null) {
filterChain.doFilter(httpServletRequest, servletResponse);
return;
}
onlineUser = onlineUserService.getOne(properties.getOnlineKey() + authToken);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
}
String username = StringUtils.isNotBlank(authToken) ? tokenUtil.getUsernameFromToken(authToken) : null;
if (onlineUser != null && username != null && SecurityContextHolder.getContext().getAuthentication() == null && tokenUtil.validateToken(authToken)) {
UserDetails userDetails = tokenUtil.getUserDetails(authToken);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestRri);
} else {
tokenUtil.removeToken(authToken);
log.debug("no valid JWT token found, uri: {}", requestRri);
}
filterChain.doFilter(httpServletRequest, servletResponse);
}
}

View File

@ -1,8 +1,9 @@
/**
package co.yixiang.modules.security.security; /**
* Copyright (C) 2018-2020
* All rights reserved, Designed By www.yixiang.co
*/
*//*
package co.yixiang.modules.security.security;
import co.yixiang.modules.security.config.SecurityProperties;
@ -30,9 +31,11 @@ import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;
*/
/**
* @author /
*/
*//*
@Slf4j
@Component
public class TokenProvider implements InitializingBean {
@ -112,3 +115,4 @@ public class TokenProvider implements InitializingBean {
return null;
}
}
*/

View File

@ -0,0 +1,238 @@
package co.yixiang.modules.security.security;
import co.yixiang.modules.security.config.SecurityProperties;
import co.yixiang.modules.security.security.vo.JwtUser;
import co.yixiang.utils.RedisUtils;
import co.yixiang.utils.StringUtils;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.Date;
/**
* Token 工具类
*
* @author lioncity
*/
@Component
public class TokenUtil {
@Autowired
private SecurityProperties properties;
/**
* Logger
*/
protected static final Logger LOGGER = LoggerFactory.getLogger(TokenUtil.class);
/**
* 权限缓存前缀
*/
private static final String REDIS_PREFIX_AUTH = "auth:";
/**
* 用户信息缓存前缀
*/
private static final String REDIS_PREFIX_USER = "user-details:";
/**
* redis repository
*/
@Autowired
private RedisUtils redisUtils;
/**
* 获取用户名
*
* @param token Token
* @return String
*/
public String getUsernameFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.getSubject() : null;
}
/**
* 获取过期时间
*
* @param token Token
* @return Date
*/
public Date getExpiredFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.getExpiration() : null;
}
/**
* 获得 Claims
*
* @param token Token
* @return Claims
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(properties.getSecret())
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.warn("getClaimsFromToken exception", e);
claims = null;
}
return claims;
}
/**
* 计算过期时间
*
* @return Date
*/
private Date generateExpired() {
return new Date(System.currentTimeMillis() + properties.getTokenValidityInSeconds() * 1000);
}
/**
* 判断 Token 是否过期
*
* @param token Token
* @return Boolean
*/
private Boolean isTokenExpired(String token) {
Date expirationDate = getExpiredFromToken(token);
return expirationDate.before(new Date());
}
/**
* 生成 Token
*
* @param userDetails 用户信息
* @return String
*/
public String generateToken(UserDetails userDetails) {
String secret=properties.getSecret();
String token = Jwts.builder()
.setSubject(userDetails.getUsername())
.setExpiration(generateExpired())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
String key = REDIS_PREFIX_AUTH + userDetails.getUsername() + ":" + token;
redisUtils.set(key, token, properties.getTokenValidityInSeconds() / 1000);
putUserDetails(userDetails);
return token;
}
/**
* 验证 Token
*
* @param token Token
* @return Boolean
*/
public Boolean validateToken(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_AUTH + username+ ":" + token;
Object data = redisUtils.get(key);
String redisToken = data == null ? null : data.toString();
return StringUtils.isNotEmpty(token) && !isTokenExpired(token) && token.equals(redisToken);
}
/**
* 移除 Token
*
* @param token Token
*/
public void removeToken(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_AUTH + username+ ":" + token;
redisUtils.del(key);
delUserDetails(username);
}
/**
* 获得用户信息 Json 字符串
*
* @param token Token
* @return String
*/
protected String getUserDetailsString(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_USER + username;
Object data = redisUtils.get(key);
return data == null ? null : data.toString();
}
/**
* 获得用户信息
*
* @param token Token
* @return UserDetails
*/
public UserDetails getUserDetails(String token) {
String userDetailsString = getUserDetailsString(token);
if (userDetailsString != null) {
return new Gson().fromJson(userDetailsString, JwtUser.class);
}
return null;
}
/**
* 存储用户信息
*
* @param userDetails 用户信息
*/
private void putUserDetails(UserDetails userDetails) {
String key = REDIS_PREFIX_USER + userDetails.getUsername();
redisUtils.set(key, new Gson().toJson(userDetails), properties.getTokenValidityInSeconds() / 1000);
}
/**
* 删除用户信息
*
* @param username 用户名
*/
private void delUserDetails(String username) {
String key = REDIS_PREFIX_USER + username;
redisUtils.del(key);
}
public String getToken(HttpServletRequest request) {
final String requestHeader = request.getHeader(properties.getHeader());
if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
return requestHeader.substring(7);
}
return null;
}
public static void main(String[] args) {
String key = Base64.getEncoder().encodeToString("123".getBytes());
Claims claims = Jwts.parser().setSigningKey(key)
.parseClaimsJws("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFwcCIsIndyaXRlIl0sInVpbiI6MSwiZXhwIjoxNTc1MDE1ODgzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjdiYjQ1NTQtNTQ4OS00YTg5LWI3NjQtNzNjODI0YzljNGMyIiwiY2xpZW50X2lkIjoibHZoYWliYW8ifQ.x7QZxRAR1wuX_YNLi6EzRJ1iaKr1rIEUgjtYF0oSx5k").getBody();
System.out.println(JSON.toJSONString(claims));
}
}

View File

@ -9,6 +9,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.sql.Timestamp;
@ -46,8 +47,7 @@ public class JwtUser implements UserDetails {
private final String job;
@JsonIgnore
private final Collection<GrantedAuthority> authorities;
private final Collection<SimpleGrantedAuthority> authorities;
private final boolean enabled;
private Timestamp createTime;

View File

@ -16,6 +16,7 @@ import co.yixiang.modules.system.service.dto.RoleSmallDto;
import co.yixiang.modules.system.service.dto.UserDto;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@ -107,7 +108,7 @@ public interface RoleService extends BaseService<Role>{
* @param user 用户信息
* @return 权限信息
*/
Collection<GrantedAuthority> mapToGrantedAuthorities(UserDto user);
Collection<SimpleGrantedAuthority> mapToGrantedAuthorities(UserDto user);
void delete(Set<Long> ids);
}

View File

@ -241,7 +241,7 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, Role> implement
*/
@Override
// @Cacheable(key = "'loadPermissionByUser:' + #p0.username")
public Collection<GrantedAuthority> mapToGrantedAuthorities(UserDto user) {
public Collection<SimpleGrantedAuthority> mapToGrantedAuthorities(UserDto user) {
Set<Role> roles = roleMapper.findByUsers_Id(user.getId());
for (Role role : roles) {
Set<Menu> menuSet = menuMapper.findMenuByRoleId(role.getId());

View File

@ -54,6 +54,7 @@ jwt:
header: Authorization
# 令牌前缀
token-start-with: Bearer
secret: k09BQnaF
# 必须使用最少88位的Base64对该令牌进行编码
base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
# 令牌过期时间 此处单位/毫秒 默认4小时可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html

View File

@ -57,6 +57,7 @@ jwt:
header: Authorization
# 令牌前缀
token-start-with: Bearer
secret: k09BQnaF
# 必须使用最少88位的Base64对该令牌进行编码
base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
# 令牌过期时间 此处单位/毫秒 默认2小时可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html