SpringSecurity+JWT实现前后端分离的使用#yyds干货盘点#

家资是何物,积帙列梁梠。这篇文章主要讲述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干货盘点#】现在没法在线预览了
结合前端跑起来的样子如下

    推荐阅读