前述
SpringSecurity整合JWT实现无状态登录
引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
yml配置
spring:
redis:
host: localhost
port: 6379
database: 0
password:
timeout: 10s
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1ms
datasource:
url: jdbc:mysql://127.0.0.1:3306/security-simple?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: admin
type: com.alibaba.druid.pool.DruidDataSource
thymeleaf:
cache: false
mvc:
static-path-pattern: /static/**
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
type-aliases-package: com.example.jwt.project.sys.entity.*
type-aliases-super-type: java.lang.Object
global-config:
db-config:
id-type: auto
configuration:
aggressive-lazy-loading: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
token:
header: Authorization
secret: guhjdsagyuhj5678324hfygf
expireTime: 30
新增TokenManager管理器
@Component
@Slf4j
public class TokenManager
{
@Value("${token.header}")
private String header;
@Value("${token.secret}")
private String secret;
@Value("${token.expireTime}")
private int expireTime;
protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
private final RedisCache redisCache;
public TokenManager(RedisCache redisCache) {
this.redisCache = redisCache;
}
public LoginUser getLoginUser(HttpServletRequest request)
{
String token = getToken(request);
if (StrUtil.isNotEmpty(token))
{
try
{
Claims claims = parseToken(token);
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
return redisCache.getCacheObject(userKey);
}
catch (Exception ignored)
{
}
}
return null;
}
public void setLoginUser(LoginUser loginUser)
{
if (ObjectUtil.isNotNull(loginUser) && StrUtil.isNotEmpty(loginUser.getToken()))
{
refreshToken(loginUser);
}
}
public void removeToken(String token)
{
if (StrUtil.isNotEmpty(token))
{
String userKey = getTokenKey(token);
redisCache.deleteObject(userKey);
}
}
public String createToken(LoginUser loginUser)
{
String uuid = IdUtil.fastUUID();
loginUser.setToken(uuid);
refreshToken(loginUser);
Map<String, Object> claims = new HashMap<>(8);
claims.put(Constants.LOGIN_USER_KEY, uuid);
return createToken(claims);
}
public void verifyToken(LoginUser loginUser)
{
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
{
refreshToken(loginUser);
}
}
public void refreshToken(LoginUser loginUser)
{
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
String userKey = getTokenKey(loginUser.getToken());
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
public String createToken(Map<String, Object> claims)
{
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
private Claims parseToken(String token)
{
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
private String getToken(HttpServletRequest request)
{
String token = request.getHeader(header);
if (StrUtil.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
private String getTokenKey(String uuid)
{
return Constants.LOGIN_TOKEN_KEY + uuid;
}
}
新增JWT自定义认证过滤器
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
private final TokenManager tokenManager;
public JwtAuthenticationTokenFilter(TokenManager tokenManager) {
this.tokenManager = tokenManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
LoginUser loginUser = tokenManager.getLoginUser(request);
if (ObjectUtil.isNotNull(loginUser) && ObjectUtil.isNull(SecurityUtils.getAuthentication()))
{
tokenManager.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request,response);
}
}
修改退出处理器
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
private final TokenManager tokenManager;
public LogoutSuccessHandlerImpl(TokenManager tokenManager)
{
this.tokenManager = tokenManager;
}
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication e) throws IOException {
LoginUser loginUser = tokenManager.getLoginUser(request);
if(ObjectUtil.isNotNull(loginUser))
{
tokenManager.removeToken(loginUser.getToken());
}
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功")));
}
}
修改SpringSecurity配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig
{
private final MyUserDetailsServiceImpl userDetailsService;
private final LogoutSuccessHandlerImpl logoutSuccessHandler;
private final AuthenticationEntryPointImpl unauthorizedHandler;
private final CustomExpiredSessionStrategyImpl expiredSessionStrategy;
private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
public SecurityConfig(TokenManager tokenManager, RedisCache redisCache, MyUserDetailsServiceImpl userDetailsService, LogoutSuccessHandlerImpl logoutSuccessHandler,
AuthenticationEntryPointImpl unauthorizedHandler,
CustomExpiredSessionStrategyImpl expiredSessionStrategy,
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter) {
this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter;
this.userDetailsService = userDetailsService;
this.logoutSuccessHandler = logoutSuccessHandler;
this.unauthorizedHandler = unauthorizedHandler;
this.expiredSessionStrategy = expiredSessionStrategy;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
AuthenticationManager authenticationManager = configuration.getAuthenticationManager();
return configuration.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception
{
http.authorizeRequests(authorize ->
authorize.mvcMatchers("/userLogin","/noPermission").permitAll()
.anyRequest().authenticated()
)
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.headers().frameOptions().disable();
http.logout(logout -> logout
.logoutUrl("/logout")
.deleteCookies("JSESSIONID")
.logoutSuccessHandler(logoutSuccessHandler));
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
http.cors(withDefaults());
http.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).accessDeniedPage("/noPermission");
http.sessionManagement(session -> session
.maximumSessions(1)
.expiredSessionStrategy(expiredSessionStrategy));
http.userDetailsService(userDetailsService);
return http.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource()
{
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080/","https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
修改登录接口
@RestController
public class LoginController
{
@Autowired
ISysUserService userService;
@Autowired
SysLoginService loginService;
@PostMapping("userLogin")
public AjaxResult login(SysUser user)
{
if (StrUtil.isEmpty(user.getUsername()) || StrUtil.isEmpty(user.getPassword()))
{
return AjaxResult.error("用户名或密码未输入!");
}
String token = loginService.login(user.getUsername(),user.getPassword());
return AjaxResult.success(Constants.TOKEN, token);
}
}
登录业务逻辑代码
@Component
public class SysLoginService
{
@Autowired
private TokenManager tokenManager;
@Resource
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Autowired
private ISysUserService userService;
public String login(String username, String password)
{
Authentication authentication;
try
{
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
throw new ServiceException("用户名密码错误");
}
else
{
throw new ServiceException(e.getMessage());
}
}
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
return tokenManager.createToken(loginUser);
}
}
测试效果图




博客
我的博客
添加友链