maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
用户登录接口
public LoginUser login(String username, String password) throws JsonProcessingException {
Authentication authentication = null;
try {
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (Exception e) {
e.printStackTrace();
}
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
loginUser.setIpaddr("127.0.0.1");
loginUser.setLoginTime(new Date().getTime());
loginUser.setExpireTime(new Date().getTime() + JwtTokenUtils.REDIS_TOKEN_EXPIRATION);
String token = JwtTokenUtils.createToken(loginUser);
loginUser.setToken(token);
redisTemplate.opsForValue().set("sys:token:string:" + token, token, JwtTokenUtils.REDIS_TOKEN_EXPIRATION, TimeUnit.MILLISECONDS);
return loginUser;
}
过滤器实现请求拦截
package com.pug.security.jwt;
import com.pug.config.PugRedisCacheTemplate;
import com.pug.pojo.LoginUser;
import com.pug.pojo.SysLoginUser;
import com.pug.service.jwt.IJwtBlackService;
import com.pug.service.jwt.JwtTokenUtils;
import com.pug.service.user.ILoginUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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;
import java.util.concurrent.TimeUnit;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
@Qualifier("jwtBlackStringService")
private IJwtBlackService jwtBlackService;
@Autowired
private ILoginUserService loginUserService;
@Autowired
private PugRedisCacheTemplate pugRedisCacheTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = JwtTokenUtils.getJwtToken(request);
if (StringUtils.isEmpty(token)) {
chain.doFilter(request, response);
}
if (jwtBlackService.isBlackList(token)) {
throw new RuntimeException("token is expired black list");
}
boolean isverfiy = JwtTokenUtils.isverfiy(token);
String tokenKey = "sys:token:string:" + token;
if (!isverfiy) {
token = pugRedisCacheTemplate.getCacheObject(tokenKey);
}
LoginUser loginUser = JwtTokenUtils.getJwtTokenLoginUser(token);
if (loginUser == null || pugRedisCacheTemplate.getCacheObject(tokenKey) == null) {
throw new RuntimeException("token is expired login!!!");
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
if (pugRedisCacheTemplate.getExpireTime(tokenKey) < 60 * 40) {
String newToken = JwtTokenUtils.createToken(loginUser);
pugRedisCacheTemplate.setCacheObject(tokenKey, newToken, JwtTokenUtils.REDIS_TOKEN_EXPIRATION, TimeUnit.MILLISECONDS);
}
chain.doFilter(request, response);
}
}
security添加拦截器
package com.pug.security.config;
import com.pug.security.handler.MyAccessDeniedHandler;
import com.pug.security.jwt.JwtAuthenticationTokenFilter;
import com.pug.service.jwt.JwtTokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.header.Header;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import javax.annotation.Resource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityWebConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService loginUserDetailService;
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private CorsFilter corsFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(loginUserDetailService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/login", "/register", "/captchaImage").permitAll()
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/profile/**"
).permitAll()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
.antMatchers("/druid/**").permitAll()
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
// 添加JWT filter
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
http.addFilterBefore(corsFilter , JwtAuthenticationTokenFilter.class);
http.addFilterBefore(corsFilter , LogoutFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
JWT工具类
package com.pug.service.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.pug.config.FastJsonUtil;
import com.pug.pojo.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class JwtTokenUtils {
public final static String TOKEN_HEADER = "Authorization";
public final static String TOKEN_PREFIX = "Bearer ";
public final static String DEFAULT_SECRET = "abcdefghijklmnoqprstuvwxyz";
public final static long ONE_MINUTES = 60 * 1000;
public final static long TOKEN_EXPIRATION = 30 * ONE_MINUTES;
public final static long REDIS_TOKEN_EXPIRATION = 2 * TOKEN_EXPIRATION;
private static final Algorithm algorithm = Algorithm.HMAC256(DEFAULT_SECRET);
public static String createToken(LoginUser sysLoginUser) throws JsonProcessingException {
Map<String, Object> headerClaims = new HashMap<>();
headerClaims.put("typ", "ZIP");
return JWT.create()
.withClaim("token", FastJsonUtil.toJSON(sysLoginUser))
.withHeader(headerClaims)
.withSubject(sysLoginUser.getId() + "")
.withIssuer(sysLoginUser.getUsername())
.withIssuedAt(generateCurrentDate())
.withExpiresAt(generateExpirationDate())
.sign(algorithm);
}
public static String parseToken(String token) {
String userId;
try {
userId = getJWTFromToken(token).getSubject();
} catch (Exception e) {
userId = null;
}
return userId;
}
public static boolean isverfiy(String token) {
try {
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(token);
return true;
} catch (Exception ex) {
return false;
}
}
public static DecodedJWT getJWTFromToken(String token) {
DecodedJWT jwt;
try {
JWTVerifier verifier = JWT.require(algorithm).build();
jwt = verifier.verify(token);
return jwt;
} catch (Exception ex) {
jwt = null;
log.info("token is expired....");
}
return jwt;
}
public static String getJwtToken(HttpServletRequest request) {
String header = request.getHeader(TOKEN_HEADER);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
log.info("请求头不含JWT token, 调用下个过滤器");
return null;
}
String token = header.split(" ")[1].trim();
return token;
}
public static LoginUser getJwtTokenLoginUser(String token) {
try {
DecodedJWT jwtFromToken = JwtTokenUtils.getJWTFromToken(token);
if (jwtFromToken == null) return null;
Claim claim = jwtFromToken.getClaim("token");
String asString = claim.asString();
return FastJsonUtil.toBean(asString, LoginUser.class);
} catch (Exception ex) {
return null;
}
}
private static Date generateCurrentDate() {
return new Date();
}
private static Date generateExpirationDate() {
return new Date(DateTime.now().getMillis() + TOKEN_EXPIRATION);
}
}
SpringSecurity授权流程
security过滤器链
|