首先导入security框架的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
在yml配置文件中编写关于token管理的基本配置
jwt:
# JWT存储请求头
tokenHeader: Authorization
# JWT加密使用的密钥,自设即可
secret: ????
# JWT超期限时间(60*60*24)
expiration: 604800
# JWT负载中拿到开头
tokenHead: Bearer
创建jwt工具类
@Component
public class JwtTokenUtil {
private static final String CLAIM_KEY_USERNAME="sub";
private static final String CLAIM_KEY_CREATED="created";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* 根据用户信息生成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());
return generateToken(claims);
}
// 根据荷载JWT生成token
public String generateToken(Map<String,Object> claims){
// Jwts去生成token
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS256,secret)
.compact();
}
// 生成token失效时间
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis()+expiration*1000);
}
// 从token中获取登录名
public String getUserNameByToken(String token){
String userName;
try{
Claims claims=getClaimsFromToken(token);
userName=claims.getSubject();
}catch (Exception e){
userName=null;
}
return userName;
}
// 从token中获取荷载
private Claims getClaimsFromToken(String token) {
Claims claims=null;
try {
claims=Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
e.printStackTrace();
}
return claims;
}
// 验证token是否有效
public boolean validateToken(String token,UserDetails userDetails){
String username=getUserNameByToken(token);
return username.equals(userDetails.getUsername())&&!isTokenExpired(token);
}
// 验证token是否失效
private boolean isTokenExpired(String token) {
Date expireDate=getExpiredDateFromToken(token);
return expireDate.before(new Date());
}
// 从token中获取失效时间
private Date getExpiredDateFromToken(String token) {
Claims claims=getClaimsFromToken(token);
return claims.getExpiration();
}
// 判断token是否可以被刷新
public boolean canRefresh(String token){
return !isTokenExpired(token);
}
// 刷新token
public String refreshToken(String token){
Claims claims=getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED,new Date());
return generateToken(claims);
}
}
创建登录过滤器,验证token登录权限,注意该类不需要添加@Component
@Configuration注解来注入bean,否则后续会导致security放行失败从而拦截了登录接口
public class JwtDecoderFilter extends OncePerRequestFilter {
@Value("${jwt.tokenHead}")
private String tokenHead;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private SecurityUserService securityUserService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader(tokenHeader);
//判断是否能根据Header拿到对应的值以及值的开头是否为tokenHead
if(authHeader != null && authHeader.startsWith(tokenHead)){
//存在token
String authToken = authHeader.substring(tokenHead.length());
String username = jwtTokenUtil.getUserNameByToken(authToken);
//token存在用户名但未登录
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = securityUserService.loadUserByUsername(username);
//验证token是否有效,重新设置用户对象
if(jwtTokenUtil.validateToken(authToken,userDetails)){
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
// 放行
filterChain.doFilter(request, response);
}
}
创建springsecurity配置类,在这里使用@Bean注解来注入过滤器的bean,交给spring管理
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Autowired
private RestAuthorizationEntryPoint point;
@Autowired
private RestfulAccessDeniedHandler handler;
@Bean
public JwtDecoderFilter jwtDecoderFilter(){
return new JwtDecoderFilter();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/login","/logout");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable() //csrf关闭 如果自定义登录 需要关闭
.authorizeRequests() //开启登录认证
.anyRequest() //所有请求
.authenticated()
.and()//需要登录才能访问
.cors(); //允许跨域配置,前后端分离项目注意添加
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 添加JWT 登录授权过滤器
http.addFilterBefore(jwtDecoderFilter(), UsernamePasswordAuthenticationFilter.class);
//添加自定义未授权和未登录返回结果
http.exceptionHandling()
.accessDeniedHandler(handler)
.authenticationEntryPoint(point);
}
}
可以自定义添加未授权和未登录的返回结果
/**
* 当未登录或者token失效时,访问接口自定义返回结果
*/
@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter out = response.getWriter();
Result<Object> error = Result.fail(ResultCodeEnum.LOGIN_AUTH);
out.write(new ObjectMapper().writeValueAsString(error));
out.flush();
out.close();
}
}
/**
* 当访问接口没有权限时,自定义返回结果
*/
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter out = response.getWriter();
Result<Object> error = Result.fail(ResultCodeEnum.PERMISSION);
out.write(new ObjectMapper().writeValueAsString(error));
out.flush();
out.close();
}
}