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);
}
@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
@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-api0.11.2 io.jsonwebtoken jjwt-impl0.11.2 runtime io.jsonwebtoken jjwt-jackson0.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;
@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);
}
private String generateToken(Map claims) {
SecretKey secretKey = getSecretKey();
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(secretKey)
.compact();
}
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();
}
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;
}
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUserNameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
}
private Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
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);
}
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);
}
}
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() {
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
到此这篇关于SpringSecurity+JWT实现前后端分离的使用详解的文章就介绍到这了,更多相关SpringSecurity+JWT前后端分离内容请搜索考高分网以前的文章或继续浏览下面的相关文章希望大家以后多多支持考高分网!



