SpringSecurity+JWT实现前后端分离的使用#yyds干货盘点#
Posted Java4ye
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSecurity+JWT实现前后端分离的使用#yyds干货盘点#相关的知识,希望对你有一定的参考价值。
SpringSecurity+JWT实现前后端分离的使用
创建一个配置类 SecurityConfig 继承 WebSecurityConfigurerAdapter
package top.ryzeyang.demo.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationManager;
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.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import top.ryzeyang.demo.common.filter.JwtAuthenticationTokenFilter;
import top.ryzeyang.demo.utils.JwtTokenUtil;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
final AuthenticationFailureHandler authenticationFailureHandler;
final AuthenticationSuccessHandler authenticationSuccessHandler;
final AuthenticationEntryPoint authenticationEntryPoint;
final AccessDeniedHandler accessDeniedHandler;
final LogoutSuccessHandler logoutSuccessHandler;
public SecurityConfig(AuthenticationFailureHandler authenticationFailureHandler, AuthenticationSuccessHandler authenticationSuccessHandler, AuthenticationEntryPoint authenticationEntryPoint, AccessDeniedHandler accessDeniedHandler, LogoutSuccessHandler logoutSuccessHandler)
this.authenticationFailureHandler = authenticationFailureHandler;
this.authenticationSuccessHandler = authenticationSuccessHandler;
this.authenticationEntryPoint = authenticationEntryPoint;
this.accessDeniedHandler = accessDeniedHandler;
this.logoutSuccessHandler = logoutSuccessHandler;
@Bean
public PasswordEncoder passwordEncoder()
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
@Bean("users")
public UserDetailsService users()
UserDetails user = User.builder()
.username("user")
.password("bcrypt$2a$10$1.NSMxlOyMgJHxOi8CWwxuU83G0/HItXxRoBO4QWZMTDp0tzPbCf.")
.roles("USER")
// roles 和 authorities 不能并存
// .authorities(new String[]"system:user:query", "system:user:edit", "system:user:delete")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("bcrypt$2a$10$1.NSMxlOyMgJHxOi8CWwxuU83G0/HItXxRoBO4QWZMTDp0tzPbCf.")
// .roles("USER", "ADMIN")
.roles("ADMIN")
// .authorities("system:user:create")
.build();
return new InMemoryUserDetailsManager(user, admin);
/**
* 角色继承:
* 让Admin角色拥有User的角色的权限
* @return
*/
@Bean
public RoleHierarchy roleHierarchy()
RoleHierarchyImpl result = new RoleHierarchyImpl();
result.setHierarchy("ROLE_ADMIN > ROLE_USER");
return result;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(users());
@Override
public void configure(WebSecurity web) throws Exception
web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
@Override
protected void configure(HttpSecurity http) throws Exception
// 自定义异常处理
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
// 权限
.and()
.authorizeRequests()
// 一个是必须待身份信息但是不校验权限。
.antMatchers("/", "/mock/login").permitAll()
//只允许匿名访问
.antMatchers("/hello").anonymous()
.anyRequest()
.authenticated()
// 表单登录
// .and()
// .formLogin()
// .successHandler(authenticationSuccessHandler)
// .failureHandler(authenticationFailureHandler)
// .loginProcessingUrl("/login")
// .permitAll()
// 注销
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll()
// 关闭csrf 会在页面中生成一个csrf_token
.and()
.csrf()
.disable()
// 基于token,所以不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 添加我们的JWT过滤器
.and()
.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)
;
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter()
return new JwtAuthenticationTokenFilter();
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
return super.authenticationManagerBean();
@Bean
public JwtTokenUtil jwtTokenUtil()
return new JwtTokenUtil();
创建JWT过滤器 继承 OncePerRequestFilter
package top.ryzeyang.demo.common.filter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import top.ryzeyang.demo.utils.JwtTokenUtil;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* JWT登录授权过滤器
*
* @author macro
* @date 2018/4/26
*/
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
@Qualifier("users")
@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))
String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
String username = jwtTokenUtil.getUserNameFromToken(authToken);
log.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));
log.info("authenticated user:", username);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
自定义handler
这里主要介绍两个hanler,一个权限不足,一个验证失败的
权限不足 实现 AccessDeniedHandler
package top.ryzeyang.demo.common.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import top.ryzeyang.demo.common.api.CommonResult;
import top.ryzeyang.demo.common.api.ResultEnum;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Component
public class MyAccessDeineHandler implements AccessDeniedHandler
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
writer.write(new ObjectMapper().writeValueAsString(new CommonResult<>(ResultEnum.ACCESS_ERROR, e.getMessage())));
writer.flush();
writer.close();
认证失败 实现 AuthenticationEntryPoint
如账号密码错误等验证不通过时
package top.ryzeyang.demo.common.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import top.ryzeyang.demo.common.api.CommonResult;
import top.ryzeyang.demo.common.api.ResultEnum;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
// 认证失败
writer.write(new ObjectMapper().writeValueAsString(new CommonResult<>(ResultEnum.AUTHENTICATION_ERROR, e.getMessage())));
writer.flush();
writer.close();
JWT工具类
稍微改了一点,因为 JDK11 用的 jjwt 版本不一样 ,语法也有些不同
pom 文件中引入 jjwt
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
JwtTokenUtil
package top.ryzeyang.demo.utils;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JwtToken生成的工具类
* JWT token的格式:header.payload.signature
* header的格式(算法、token的类型):
* "alg": "HS512","typ": "JWT"
* payload的格式(用户名、创建时间、生成时间):
* "sub":"wang","created":1489079981393,"exp":1489684781
* signature的生成算法:
* HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
*
* @author macro
* @date 2018/4/26
*/
@Slf4j
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;
@Value("$jwt.tokenHead")
private String tokenHead;
private SecretKey getSecretKey()
byte[] encodeKey = Decoders.BASE64.decode(secret);
return Keys.hmacShaKeyFor(encodeKey);
/**
* 根据负责生成JWT的token
*/
private String generateToken(Map<String, Object> claims)
SecretKey secretKey = getSecretKey();
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(secretKey)
.compact();
/**
* 测试生成的token
* @param claims
* @return
*/
public String generateToken2(Map<String, Object> claims)
SecretKey secretKey = getSecretKey();
return Jwts.builder()
.setClaims(claims)
.setIssuer("Java4ye")
.setExpiration(new Date(System.currentTimeMillis() + 1 * 1000))
.signWith(secretKey)
.compact();
/**
* 从token中获取JWT中的负载
*/
private Claims getClaimsFromToken(String token)
SecretKey secretKey = getSecretKey();
Claims claims = null;
try
claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
catch (Exception e)
log.info("JWT格式验证失败:", token);
return claims;
/**
* 生成token的过期时间
*/
private Date generateExpirationDate()
return new Date(System.currentTimeMillis() + expiration * 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()) && !isTokenExpired(token);
/**
* 判断token是否已经失效
*/
private boolean isTokenExpired(String token)
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
/**
* 从token中获取过期时间
*/
private Date getExpiredDateFromToken(String token)
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
/**
* 根据用户信息生成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);
/**
* 当原来的token没过期时是可以刷新的
*
* @param oldToken 带tokenHead的token
*/
public String refreshHeadToken(String oldToken)
if (StrUtil.isEmpty(oldToken))
return null;
String token = oldToken.substring(tokenHead.length());
if (StrUtil.isEmpty(token))
return null;
//token校验不通过
Claims claims = getClaimsFromToken(token);
if (claims == null)
return null;
//如果token已经过期,不支持刷新
if (isTokenExpired(token))
return null;
//如果token在30分钟之内刚刷新过,返回原token
if (tokenRefreshJustBefore(token, 30 * 60))
return token;
else
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
/**
* 判断token在指定时间内是否刚刚刷新过
*
* @param token 原token
* @param time 指定时间(秒)
*/
private boolean tokenRefreshJustBefore(String token, int time)
Claims claims = getClaimsFromToken(token);
Date created = claims.get(CLAIM_KEY_CREATED, Date.class);
Date refreshDate = new Date();
//刷新时间在创建时间的指定时间内
if (refreshDate.after(created) && refreshDate.before(DateUtil.offsetSecond(created, time)))
return true;
return false;
配置文件 application.yml
这里的 secret
可以用该方法生成
@Test
void generateKey()
/**
* SECRET 是签名密钥,只生成一次即可,生成方法:
* Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
* String secretString = Encoders.BASE64.encode(key.getEncoded()); # 本文使用 BASE64 编码
* */
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS512);
String secretString = Encoders.BASE64.encode(key.getEncoded());
System.out.println(secretString);
// Blk1X8JlN4XH4s+Kuc0YUFXv+feyTgVUMycSiKbiL0YhRddy872mCNZBGZIb57Jn2V1RtaFXIxs8TvNPsnG//g==
jwt:
tokenHeader: Authorization
secret: Blk1X8JlN4XH4s+Kuc0YUFXv+feyTgVUMycSiKbiL0YhRddy872mCNZBGZIb57Jn2V1RtaFXIxs8TvNPsnG//g==
expiration: 604800
tokenHead: Bearer
server:
servlet:
context-path: /api
Controller
AuthController
package top.ryzeyang.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;
import top.ryzeyang.demo.common.api.CommonResult;
import top.ryzeyang.demo.model.dto.UserDTO;
import top.ryzeyang.demo.utils.CommonResultUtil;
import top.ryzeyang.demo.utils.JwtTokenUtil;
import java.util.Collection;
@RestController
@ResponseBody
@RequestMapping("/mock")
public class AuthController
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Qualifier("users")
@Autowired
private UserDetailsService userDetailsService;
@Autowired
AuthenticationManager authenticationManager;
@GetMapping("/userinfo")
public CommonResult<Collection<? extends GrantedAuthority>> getUserInfo()
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
return CommonResultUtil.success(authorities);
/**
* 模拟登陆
*/
@PostMapping("/login")
public CommonResult<String> login(@RequestBody UserDTO userDTO)
String username = userDTO.getUsername();
String password = userDTO.getPassword();
UsernamePasswordAuthenticationToken token
= new UsernamePasswordAuthenticationToken(username, password);
Authentication authenticate = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticate);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
String t = jwtTokenUtil.generateToken(userDetails);
return CommonResultUtil.success(t);
HelloController
package top.ryzeyang.demo.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController
@GetMapping("/hello")
public String hello()
return "hello";
@GetMapping("/hello/anonymous")
public String hello2()
return "anonymous";
@PreAuthorize("hasRole(ADMIN)")
@GetMapping("/hello/admin")
public String helloAdmin()
return "hello Admin";
@PreAuthorize("hasRole(USER)")
@GetMapping("/hello/user")
public String helloUser()
return "hello user";
@PreAuthorize("hasAnyAuthority(system:user:query)")
@GetMapping("/hello/user2")
public String helloUser2()
return "hello user2";
项目地址在GitHub上
地址:SpringSecurity-Vuetify-Permissions-demo :https://github.com/RyzeYang/SpringSecurity-Vuetify-Permissions-demo
现在没法在线预览了
结合前端跑起来的样子如下
以上是关于SpringSecurity+JWT实现前后端分离的使用#yyds干货盘点#的主要内容,如果未能解决你的问题,请参考以下文章
Spring boot+Spring security+JWT实现前后端分离登录认证及权限控制
SpringBoot + MyBatis-plus + SpringSecurity + JWT实现用户无状态请求验证(前后端分离)
spring boot整合jwt 实现前后端分离登录认证及授权