springboot+SpringSecurity+JWT
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--JWT(Json Web Token)登录支持-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
新建一个SecurityConfig配置类,创建一个UserDetailsService接口的Bean,用来获取用户信息。
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
.authorizeRequests();
//不需要保护的资源路径允许访问
for (String url : ignoreUrlsConfig.getUrls()) {
registry.antMatchers(url).permitAll();
}
//允许跨域请求的OPTIONS请求
registry.antMatchers(HttpMethod.OPTIONS)
.permitAll();
// 任何请求需要身份认证
registry.and()
.authorizeRequests()
.anyRequest()
.authenticated()
// 关闭跨站请求防护及不使用session
.and()
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 自定义权限拒绝处理类
.and()
.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint)
// 自定义权限拦截器JWT过滤器
.and()
.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
//获取登录用户信息
return username -> {
UserEntity userDbModel = userService.getUserByUsername(username);
if (userDbModel != null) {
List<PermissionEntity> permissionList = userService.getPermissionListByUsername(userDbModel.getUsername());
return new QhdflUserDetails(userDbModel,permissionList);
}
throw new UsernameNotFoundException("用户名或密码错误");
};
}
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
return new JwtAuthenticationTokenFilter();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
新建一个jwt过滤器
/**
* @description: JWT登录授权过滤器 在用户名和密码校验前添加的过滤器,如果请求中有jwt的token且有效,会取出token中的用户名,然后调用SpringSecurity的API进行登录操作。
* @author: zchi
* @create: 2020-02-08
*/
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader(this.tokenHeader);
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
// The part after "Bearer "
String authToken = authHeader.substring(this.tokenHead.length());
String username = jwtTokenUtil.getUserNameFromToken(authToken);
LOGGER.info("checking username:{}", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
LOGGER.info("authenticated user:{}", username);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
自定义返回结果:未登录或登录过期
/**
* @description: 自定义返回结果:未登录或登录过期
* @author: zchi
* @create: 2020-02-08
*/
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println(JSONUtil.parse(Result.unauthorized(authException.getMessage())));
response.getWriter().flush();
}
}
自定义返回结果:没有访问权限
/**
* @description: 自定义返回结果:没有访问权限
* @author: zchi
* @create: 2020-02-08
*/
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
httpServletResponse.getWriter().println(JSONUtil.parse(Result.forbidden(e.getMessage())));
httpServletResponse.getWriter().flush();
}
}
jwt工具类
package com.zchi.qhdfldemo.security;
import com.zchi.qhdfldemo.util.RedisUtil;
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.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @description: JwtToken生成的工具类
* @author: zchi
* @create: 2020-02-08
*/
@Component
public class JwtTokenUtil {
private static final Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
private static final String REFRESH_TOKEN = "refresh_token";
private static final String ACCESSTOKEN = "access_Token";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long accessTokenExpiration;
private Long refreshTokenExpiration;
@Autowired
private RedisUtil redisUtil;
@Autowired
private SecurityProperties securityProperties;
// 根据用户信息生成token
public Map<String, String> generateToken1(UserDetails userDetails) {
Map<String, String> rst = new HashMap<String, String>();
// 访问token
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
rst.put(ACCESSTOKEN, generateToken(claims, createAssertTokenExpirationDate()));
rst.put(REFRESH_TOKEN, generateToken(claims, createRefershTokenExpirationDate()));
return rst;
}
// 根据用户信息生成token
public DoubleToken generateDoubleToken(UserDetails userDetails) {
DoubleToken doubleToken = new DoubleToken();
// 访问token
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
doubleToken.setAccessToken(generateToken(claims, createAssertTokenExpirationDate()));
doubleToken.setRefreshToken(generateToken(claims, createRefershTokenExpirationDate()));
return doubleToken;
}
private String generateToken(Map<String, Object> claims, Date expirationTokenDate) {
return Jwts.builder()
.setClaims(claims)
// .setExpiration(generateExpirationDate())
.setExpiration(expirationTokenDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 从token中获取JWT中的负载
*/
public Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
logger.info("JWT格式验证失败:{}",token);
}
return claims;
}
/**
*生成token的过期时间
*/
private Date createAssertTokenExpirationDate() {
return new Date(System.currentTimeMillis() + securityProperties.getAccessTokenExpiration() * 1000);
}
/**
*生成token的过期时间
*/
private Date createRefershTokenExpirationDate() {
return new Date(System.currentTimeMillis() + securityProperties.getRefreshTokenExpiration() * 1000);
}
/**
* 从token中获取登录用户名
*/
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 验证token是否还有效
*
* @param token 客户端传入的token
* @param userDetails 从数据库中查询出来的用户信息
*/
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUserNameFromToken(token);
return username.equals(userDetails.getUsername()) && !isAccessTokenExpired(token);
}
/**
* 验证token是否还有效
*
* @param token 客户端传入的token
* @param userDetails 从数据库中查询出来的用户信息
*/
public boolean validateRefreshToken(String token, UserDetails userDetails) {
String username = getUserNameFromToken(token);
return username.equals(userDetails.getUsername()) && !isRefreshTokenExpired(token);
}
/**
* 从token中获取accesstoken的到期时间
*/
public Date getAccessTokenExpiredDate(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 从令牌中获取refreshToken的到期时间
* @param token
* @return
*/
public Date getRefreshTokenExpiredDate(String token) {
Claims claims = getClaimsFromToken(token);
return (Date) claims.get(REFRESH_TOKEN);
}
/**
* 判断token是否已经失效
*/
private boolean isAccessTokenExpired(String token) {
// accessTokenExpiredDate
Date expiredDate = getAccessTokenExpiredDate(token);
return expiredDate.before(new Date());
}
/**
* 校验刷新令牌是否过期
* @param token
* @return
*/
private boolean isRefreshTokenExpired(String token) {
Date refreshExpiredDate = getRefreshTokenExpiredDate(token);
return refreshExpiredDate.before(new Date());
}
/**
* 根据用户信息生成token
*/
// public String generateToken(UserDetails userDetails) {
// //
// Map<String, Object> claims = new HashMap<>();
// claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
// claims.put(CLAIM_KEY_CREATED, new Date());
// claims.put(REFRESH_TOKEN, new Date(System.currentTimeMillis() + refreshTokenExpiration * 1000));
// return generateToken(claims);
// }
/**
* 判断token是否可以被刷新
*/
public boolean canRefresh(String token) {
return !isAccessTokenExpired(token);
}
/**
* 刷新token
*/
public String refreshToken(String oldToken) {
Date date = getAccessTokenExpiredDate(oldToken);
if (date.before(new Date())) {
return oldToken;
}
String userName = getUserNameFromToken(oldToken);
String oldRefreshToken = (String) redisUtil.get(userName + ":refreshToken");
if (oldRefreshToken != null) {
// 重新生成token
// 访问token
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userName);
claims.put(CLAIM_KEY_CREATED, new Date());
String newAccressToken = generateToken(claims, createAssertTokenExpirationDate());
String newRefreshToken = generateToken(claims, createRefershTokenExpirationDate());
redisUtil.expire(userName + ":refreshToken", securityProperties.getRefreshTokenExpiration());
return newAccressToken;
}
return null;
}
}