SpringSecurity+JWT实现前后端分离的使用详解

网友投稿 482 2023-02-18

SpringSecurity+JWT实现前后端分离的使用详解

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

这里直接用的macro大佬 mall商城里的例子

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工具类

这里直接用的macro大佬 mall商城里的例子

稍微改了一点,因为 JDK11 用的 jjwt 版本不一样 ,语法也有些不同

pom 文件中引入 jjwt

io.jsonwebtoken

jjwt-api

0.11.2

io.jsonwebtoken

jjwt-impl

0.11.2

runtime

io.jsonwebtoken

jjwt-jackson

0.11.2

runtime

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 claims) {

SecretKey secretKey = getSecretKey();

return Jwts.builder()

.setClaims(claims)

.setExpiration(generateExpirationDate())

.signWith(secretKey)

.compact();

}

/**

* 测试生成的token

* @param claims

* @return

*/

public String generateToken2(Map 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 generateExpiratikshSCaqAmonDate() {

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 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> getUserInfo(){

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

Collection extends GrantedAuthority> authorities = authentication.getAuthorities();

return CommonResultUtil.success(authorities);

}

/**

* 模拟登陆

*/

@PostMapping("/login")

public CommonResult 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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:移动应用开发需求分析(移动应用开发需求分析怎么写)
下一篇:手机vue开发小程序(vue可以开发小程序吗)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~