目录结构

添加依赖

<!-- SpringSecurity -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency> <!-- redis客户端 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> <!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.19</version>
</dependency> <!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency> <!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ybchen</groupId>
<artifactId>ybchen-SpringSecurity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ybchen-SpringSecurity</name>
<description>SpringBoot整合SpringSecurity</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除tomcat容器 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- undertow容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency> <!-- SpringSecurity -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency> <!-- redis客户端 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> <!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.19</version>
</dependency> <!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency> <!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build> </project>

pom.xml

通用工具类

package com.ybchen.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.MessageDigest; /**
* 公共工具类
*
* @author: chenyanbin 2022-11-13 19:17
*/
@Slf4j
public class CommonUtil {
/**
* 响应json数据给前端
*
* @param response
* @param content
*/
public static void sendJsonMessage(HttpServletResponse response, Object content) {
ObjectMapper objectMapper = new ObjectMapper();
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.print(objectMapper.writeValueAsString(content));
response.flushBuffer();
} catch (IOException e) {
log.info("响应json数据给前端失败:{}", e.getMessage());
} finally {
if (writer != null) {
writer.close();
}
}
} /**
* md5加密
*
* @param data
* @return
*/
public static String MD5(String data) {
try {
java.security.MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
} catch (Exception exception) {
}
return null;
}
}

CommonUtil.java

package com.ybchen.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j; import java.util.Date; /**
* jwt工具类
*
* @author: chenyanbin 2022-11-13 19:00
*/
@Slf4j
public class JWTUtil {
/**
* token过期时间,一般7天
*/
private static final long EXPIRE = 60 * 1000 * 60 * 24 * 7; /**
* 密钥
*/
private static final String SECRET = "https://www.cnblogs.com/chenyanbin/"; /**
* 令牌前缀
*/
private static final String TOKEN_PREFIX = "ybchen"; /**
* subject
*/
private static final String SUBJECT = "security_jwt"; /**
* 根据用户信息,生成令牌token
*
* @param loginInfo 登录信息
* @return
*/
public static String geneJsonWebToken(String loginInfo) {
if (loginInfo == null || "".equalsIgnoreCase(loginInfo)) {
throw new NullPointerException("loginInfo对象为空");
}
String token = Jwts.builder()
.setSubject(SUBJECT)
//payLoad,负载
.claim("loginInfo", loginInfo)
//颁布时间
.setIssuedAt(new Date())
//过期时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.signWith(SignatureAlgorithm.HS256, SECRET).compact();
return TOKEN_PREFIX + token;
} /**
* 校验令牌token
*
* @param token
* @return
*/
public static Claims checkJwt(String token) {
try {
final Claims body = Jwts.parser()
//设置签名
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody();
return body;
} catch (Exception e) {
log.error("jwt token解密失败,错误信息:{}", e);
return null;
}
}
}

JWTUtil.java

package com.ybchen.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component; import java.io.Serializable;
import java.util.Collection;
import java.util.concurrent.TimeUnit; /**
* redis工具类
*
* @author: chenyanbin 2022-11-13 19:05
*/
@Component
@Slf4j
public class RedisUtil {
@Autowired
RedisTemplate redisTemplate; /**
* 判断缓存中是否有对应的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
} /**
* 删除对应的value
*
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
} /**
* 写入缓存
*
* @param key 缓存key
* @param value 缓存value
* @param expireTime 过期时间,秒
* @return
*/
public boolean set(final String key, Object value, Long expireTime) {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
return true;
} /**
* 原子递增
*
* @param key 键
* @param expireTime 过期时间,秒
* @return
*/
public Long incr(final String key, Long expireTime) {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
Long increment = operations.increment(key);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
return increment;
} /**
* 原子递增,永不过期
*
* @param key 键
* @return
*/
public Long incr(final String key) {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
Long increment = operations.increment(key);
return increment;
} /**
* 读取缓存
*
* @param key
* @return
*/
public Object get(final String key) {
Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
} /**
* 获得缓存的key列表
* <p>
* keys token:*
* </>
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
} /**
* 哈希 添加(Map<Map<key,value>,value>)
*
* @param key 第一个Map的key
* @param hashKey 第二个Map的key
* @param value 第二个Map的value
*/
public void hashSet(String key, String hashKey, Object value) {
HashOperations<String, String, Object> hash = redisTemplate.opsForHash();
hash.put(key, hashKey, value);
} /**
* 哈希获取数据(Map<Map<key,value>,value>)
*
* @param key 第一个Map的key
* @param hashKey 第二个Map的key
* @return
*/
public Object hashGet(String key, String hashKey) {
HashOperations<String, String, Object> hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
} /**
* 哈希删除某个key(Map<Map<key,value>,value>)
*
* @param key 第一个Map的key
* @param hashKey 第二个Map的key
* @return
*/
public Long hashDelete(String key, String hashKey) {
HashOperations<String, String, Object> hash = redisTemplate.opsForHash();
return hash.delete(key, hashKey);
}
}

RedisUtil.java

package com.ybchen.utils;

import com.alibaba.fastjson2.JSON;

import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map; /**
* 统一响应工具类
*
* @author: chenyanbin 2022-11-13 18:48
*/
public class ReturnT<T> implements Serializable {
/**
* 状态码 0 表示成功,-1表示失败
*/
private Integer code;
/**
* 数据
*/
private T data;
/**
* 描述
*/
private String msg; private ReturnT() {
} private static <T> ReturnT<T> build(Integer code, T data, String msg) {
ReturnT<T> resultT = new ReturnT<>();
resultT.code = code;
resultT.data = data;
resultT.msg = msg;
return resultT;
} /**
* 成功
*
* @param data
* @return
*/
public static <T> ReturnT<T> success(T data) {
return build(0, data, null);
} /**
* 成功
* @param <T>
* @return
*/
public static <T> ReturnT<T> success() {
return build(0, null, null);
} /**
* 失败
*
* @param msg 错误信息
* @return
*/
public static <T> ReturnT<T> error(String msg) {
return build(-1, null, msg);
} /**
* 失败
*
* @param code 状态码
* @param msg 错误信息
* @return
*/
public static <T> ReturnT<T> error(int code, String msg) {
return build(code == 0 ? -1 : code, null, msg);
} /**
* 判断接口响应是否成功
*
* @param data
* @return
*/
public static boolean isSuccess(ReturnT data) {
return data.code == 0;
} /**
* 判断接口响应是否失败
*
* @param data
* @return
*/
public static boolean isFailure(ReturnT data) {
return !isSuccess(data);
} public Integer getCode() {
return code;
} public T getData() {
return data;
} public String getMsg() {
return msg;
} @Override
public String toString() {
Map<String, Object> resultMap = new LinkedHashMap<>(3);
resultMap.put("code", this.code);
resultMap.put("data", this.data);
resultMap.put("msg", this.msg);
return JSON.toJSONString(resultMap);
}
}

ReturnT.java

vo类

package com.ybchen.vo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; /**
* 登录信息
*
* @author: chenyanbin 2022-11-14 16:54
*/
public class LoginInfo implements Serializable {
/**
* 用户信息
*/
private UserVo userVo;
/**
* 权限信息
*/
private List<String> permissionsList = new ArrayList<>(); private LoginInfo() {
} public LoginInfo(UserVo userVo, List<String> permissionsList) {
this.userVo = userVo;
this.permissionsList = permissionsList;
} public UserVo getUserVo() {
return userVo;
} public void setUserVo(UserVo userVo) {
this.userVo = userVo;
} public List<String> getPermissionsList() {
return permissionsList;
} public void setPermissionsList(List<String> permissionsList) {
this.permissionsList = permissionsList;
} @Override
public String toString() {
return "LoginInfo{" +
"userVo=" + userVo +
", permissionsList=" + permissionsList +
'}';
}
}

LoginInfo.java

package com.ybchen.vo;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString; import java.io.Serializable; /**
* 用户对象
*
* @author: chenyanbin 2022-11-13 19:19
*/
@Getter
@Setter
@ToString
public class UserVo implements Serializable {
/**
* 用户id
*/
private Integer id; /**
* 用户姓名
*/
private String userName; /**
* 密码
*/
private String password; private UserVo() {
} public UserVo(Integer id, String userName, String password) {
this.id = id;
this.userName = userName;
this.password = password;
}
}

UserVo.java

常量

package com.ybchen.constant;

/**
* redis常量key
*
* @author: chenyanbin 2022-11-13 21:05
*/
public class RedisKeyConstant {
/**
* 登录信息key
*/
public static final String LOGIN_INFO_KEY = "user:login";
}

全局异常处理器

package com.ybchen.exception;

import com.ybchen.utils.ReturnT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException; /**
* 全局异常拦截器
*
* @author: chenyanbin 2022-11-13 19:39
*/
@RestControllerAdvice
@Slf4j
public class GlobalException { /**
* 请求方式有误
*
* @param e
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ReturnT httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
return ReturnT.error("请求方式有误!");
} /**
* 请求url不存在,404问题
*
* @param e
* @return
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ReturnT noHandlerFoundException(NoHandlerFoundException e) {
return ReturnT.error("请求url不存在!");
} /**
* 请求参数转换异常
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ReturnT methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
return ReturnT.error("参数转换异常:" + e.toString());
} /**
* 请求缺失参数
* @param e
* @return
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ReturnT missingServletRequestParameterException(MissingServletRequestParameterException e) {
return ReturnT.error("缺失请求参数:" + e.toString());
} /**
* SpringSecurity认证失败处理
* @param e
* @return
*/
@ExceptionHandler(BadCredentialsException.class)
public ReturnT badCredentialsException(BadCredentialsException e){
return ReturnT.error(401, "用户认证失败!");
} @ExceptionHandler(AccessDeniedException.class)
public ReturnT accessDeniedException(AccessDeniedException e){
return ReturnT.error(403,"你的权限不足!");
} /**
* 全局异常拦截
*
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public ReturnT exception(Exception e) {
log.error("全局异常:{}", e);
return ReturnT.error(e.toString());
}
}

SpringSecurity相关

重写UserDetailsService

  作用:重写该方法去数据库查找用户信息

package com.ybchen.service;

import com.alibaba.fastjson2.annotation.JSONField;
import com.ybchen.utils.CommonUtil;
import com.ybchen.vo.UserVo;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors; /**
* 默认SpringSecurity是在内存中查找用户的信息(InMemoryUserDetailsManager),
* UserDetailsService接口定义了一个根据用户名查询用户信息的方法,
* 需要重写UserDetailsService,改到去数据库中查询用户信息
*
* @author: chenyanbin 2022-11-13 19:24
*/
@Service
public class UserDetailServiceImpl implements UserDetailsService {
/**
* 模拟数据库中用户数据
*/
public static List<UserVo> userVoList = new ArrayList<UserVo>() {{
// 注意数据库中存储的密码,MD5加密过的,也可以加盐
this.add(new UserVo(1, "alex", CommonUtil.MD5("alex123")));
this.add(new UserVo(2, "tom", CommonUtil.MD5("tom123")));
}}; /**
* 获取用户信息
*
* @param userName 用户名
* @return 根据用户名找用户信息,找不到的话,返回null
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
// TODO 去数据库中,查找用户信息
List<UserVo> collect = userVoList.stream().filter(obj -> obj.getUserName().equalsIgnoreCase(userName)).collect(Collectors.toList());
// 如果用户不存在
if (collect.size() == 0) {
throw new RuntimeException("用户名不存在");
}
//TODO 根据用户id查找对应的权限信息
List<String> permissionsList = Arrays.asList("admin", "test", "hello_test", "ROLE_admin");
//将数据封装成UserDetails返回
return new LoginUserSecurity(collect.get(0), permissionsList);
} @Data
public class LoginUserSecurity implements UserDetails, Serializable {
/**
* 用户对象
*/
private UserVo userVo; private List<String> permissionsList; @JSONField(serialize = false)
private List<GrantedAuthority> authorityList; /**
* 无参构造
*/
private LoginUserSecurity() {
} /**
* 有参构造
*
* @param userVo 用户对象
* @param permissionsList 权限集合
*/
public LoginUserSecurity(UserVo userVo, List<String> permissionsList) {
this.userVo = userVo;
this.permissionsList = permissionsList;
} /**
* 权限信息
*
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (authorityList != null) {
return authorityList;
}
//将permissionsList中的String类型的权限信息,封装成SimpleGrantedAuthority对象
authorityList = permissionsList
.stream()
.map(SimpleGrantedAuthority::new)
.distinct()
.collect(Collectors.toList());
return authorityList;
} /**
* 密码
*
* @return
*/
@Override
public String getPassword() {
return this.userVo.getPassword();
} /**
* 用户名
*
* @return
*/
@Override
public String getUsername() {
return this.userVo.getId().toString();
} /**
* 账号是否过期
*
* @return
*/
@Override
public boolean isAccountNonExpired() {
return true;
} /**
* 账号是否锁定
*
* @return
*/
@Override
public boolean isAccountNonLocked() {
return true;
} /**
* 密码是否过期
*
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
} /**
* 账号是否启用
*
* @return
*/
@Override
public boolean isEnabled() {
return true;
}
}
}

  注:这里默认的用户账号和密码,实际去数据库中查询,这边模拟的数据密码md5加密,所以需要重写密码编码器

package com.ybchen.config;

import com.ybchen.utils.CommonUtil;
import org.springframework.security.crypto.password.PasswordEncoder; /**
* 自定义用户密码,重写PasswordEncoder
*
* @author: chenyanbin 2022-11-13 19:53
*/
public class Md5PasswordEncoder implements PasswordEncoder { /**
* 加密
*
* @param rawPassword 原始密码
* @return
*/
@Override
public String encode(CharSequence rawPassword) {
return CommonUtil.MD5(rawPassword.toString());
} /**
* 匹配密码
*
* @param rawPassword 原始密码
* @param encodedPassword 存储的密码
* @return
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return CommonUtil.MD5(rawPassword.toString()).equalsIgnoreCase(encodedPassword);
}
}

  将自定义密码编码器注入spring容器

package com.ybchen.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder; /**
* app配置类
* @author: chenyanbin 2022-11-13 20:12
*/
@Configuration
public class AppConfig { /**
* 自定义SpringSecurity MD5编码器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new Md5PasswordEncoder();
}
}

jwt过滤器

package com.ybchen.filter;

import com.alibaba.fastjson2.JSON;
import com.ybchen.constant.RedisKeyConstant;
import com.ybchen.utils.CommonUtil;
import com.ybchen.utils.JWTUtil;
import com.ybchen.utils.RedisUtil;
import com.ybchen.utils.ReturnT;
import com.ybchen.vo.LoginInfo;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.stream.Collectors; /**
* jwt过滤器
*
* @author: chenyanbin 2022-11-13 21:22
*/
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
RedisUtil redisUtil; @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if (token == null || "".equalsIgnoreCase(token)) {
token = request.getParameter("token");
}
if (token == null || "".equalsIgnoreCase(token)) {
//放行
filterChain.doFilter(request, response);
} else {
//解析token
Claims claims = JWTUtil.checkJwt(token);
if (claims == null) {
CommonUtil.sendJsonMessage(response, ReturnT.error("token非法"));
return;
}
//从redis中获取用户信息
Integer id = Integer.parseInt(claims.get("loginInfo").toString());
Object objValue = redisUtil.hashGet(RedisKeyConstant.LOGIN_INFO_KEY, id + "");
if (objValue == null) {
CommonUtil.sendJsonMessage(response, ReturnT.error("token过期或已注销登录"));
return;
}
LoginInfo loginInfo = JSON.parseObject(JSON.toJSONString(objValue), LoginInfo.class);
//存入SecurityContextHolder
// 获取权限信息封装到Authentication
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginInfo,
null,
loginInfo.getPermissionsList().stream()
.map(SimpleGrantedAuthority::new)
.distinct()
.collect(Collectors.toList())
);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
}

跨域处理

package com.ybchen.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /**
* 跨域配置类
* @author: chenyanbin 2022-11-14 20:47
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer { @Override
public void addCorsMappings(CorsRegistry registry) {
//设置允许跨域的路径
registry.addMapping("/**")
//设置允许跨域请求的域名
.allowedOriginPatterns("*")
//是否允许cookie
.allowCredentials(true)
//设置允许的请求方式
.allowedMethods("GET","POST","DELETE","PUT")
//设置允许的Header属性
.allowedHeaders("*")
//跨域允许时间,秒
.maxAge(3600);
}
}

redis配置类

package com.ybchen.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer; /**
* Redis配置类,处理中文乱码
* @author: chenyanbin 2022-11-13 18:47
*/
@Configuration
public class RedisTemplateConfiguration {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
//配置序列化规则
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
serializer.setObjectMapper(objectMapper);
//设置key-value序列化规则
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
//设置hash-value序列化规则
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
return redisTemplate;
}
}

SpringSecurity配置类

package com.ybchen.config;

import com.ybchen.filter.JwtFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /**
* SpringSecurity配置类
*
* @author: chenyanbin 2022-11-13 20:36
*/
@Configuration
//开启SpringSecurity的prePostEnabled配置
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
JwtFilter jwtFilter;
// @Autowired
// AuthenticationEntryPoint authenticationEntryPoint;
// @Autowired
// AccessDeniedHandler accessDeniedHandler; @Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} @Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
//对于登录接口,允许匿名访问
.antMatchers("/api/v1/user/login").anonymous()
//除了匿名访问的所有请求,全部需要鉴权认证
.anyRequest().authenticated();
//添加过滤器
http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);
//配置异常处理器,也可以用全局异常拦截器,拦截:AccessDeniedException,BadCredentialsException
// http.exceptionHandling()
// //认证失败处理器
// .authenticationEntryPoint(authenticationEntryPoint)
// //授权失败处理器
// .accessDeniedHandler(accessDeniedHandler);
//允许跨域
http.cors();
}
}

  2个异常处理器,当然也可以使用SpringBoot全局异常拦截处理,也可以写到SpringSecurity配置类中

//package com.ybchen.config;
//
//import com.ybchen.utils.CommonUtil;
//import com.ybchen.utils.ReturnT;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.security.core.AuthenticationException;
//import org.springframework.security.web.AuthenticationEntryPoint;
//import org.springframework.stereotype.Component;
//
//import javax.servlet.ServletException;
//import javax.servlet.http.HttpServletRequest;
//import javax.servlet.http.HttpServletResponse;
//import java.io.IOException;
//
///**
// * 认证失败处理器
// *
// * @author: chenyanbin 2022-11-14 13:06
// */
//@Component
//@Slf4j
//public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
//
// @Override
// public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
// log.error("认证失败:{}", e);
// CommonUtil.sendJsonMessage(response, ReturnT.error(401, "用户认证失败"));
// }
//}
//package com.ybchen.config;
//
//import com.ybchen.utils.CommonUtil;
//import com.ybchen.utils.ReturnT;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.security.access.AccessDeniedException;
//import org.springframework.security.web.access.AccessDeniedHandler;
//import org.springframework.stereotype.Component;
//
//import javax.servlet.ServletException;
//import javax.servlet.http.HttpServletRequest;
//import javax.servlet.http.HttpServletResponse;
//import java.io.IOException;
//
///**
// * 授权失败处理器
// * @author: chenyanbin 2022-11-14 13:11
// */
//@Component
//@Slf4j
//public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
//
// @Override
// public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
// log.error("授权失败:{}",e);
// CommonUtil.sendJsonMessage(response, ReturnT.error(403,"你的权限不足!"));
// }
//}

控制层

package com.ybchen.controller;

import com.ybchen.constant.RedisKeyConstant;
import com.ybchen.service.UserDetailServiceImpl;
import com.ybchen.utils.JWTUtil;
import com.ybchen.utils.RedisUtil;
import com.ybchen.utils.ReturnT;
import com.ybchen.vo.LoginInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import java.util.Objects; /**
* 用户api
*
* @author: chenyanbin 2022-11-13 20:27
*/
@RestController
@RequestMapping("/api/v1/user")
@Slf4j
public class UserController {
@Autowired
AuthenticationManager authenticationManager;
@Autowired
RedisUtil redisUtil; /**
* 用户登录
*
* @param userName 用户名
* @param password 密码
* @return
*/
@GetMapping("login")
public ReturnT login(
@RequestParam(value = "userName", required = true) String userName,
@RequestParam(value = "password", required = true) String password
) {
//AuthenticationManager authenticate进行用户认证-----》其实是调用UserDetailsService.loadUserByUsername方法
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, password);
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证没通过,给出对应的提示
if (Objects.isNull(authenticate)) {
return ReturnT.error("账号/密码错误");
}
//用户id
UserDetailServiceImpl.LoginUserSecurity loginUser = (UserDetailServiceImpl.LoginUserSecurity) authenticate.getPrincipal();
Integer userId = loginUser.getUserVo().getId();
String token = JWTUtil.geneJsonWebToken(userId.toString());
//token写入redis Hash
redisUtil.hashSet(RedisKeyConstant.LOGIN_INFO_KEY, userId + "", new LoginInfo(loginUser.getUserVo(), loginUser.getPermissionsList()));
log.info("token= \n {}", token);
return ReturnT.success(token);
} /**
* 注销登录
*
* @return
*/
@GetMapping("logout")
public ReturnT logout() {
//获取SecurityContextHolder中的用户id
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginInfo loginInfo = (LoginInfo) authentication.getPrincipal();
Integer id = loginInfo.getUserVo().getId();
//删除redis的key
redisUtil.hashDelete(RedisKeyConstant.LOGIN_INFO_KEY, id + "");
return ReturnT.success("注销成功");
}
}
package com.ybchen.controller;

import com.ybchen.utils.ReturnT;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; /**
* @author: chenyanbin 2022-11-13 18:03
*/
@RestController
public class HelloController {
/**
* -@PreAuthorize()
* ---hasAuthority:底层调用的是UserDetailsService.getAuthorities(),也就是我们重写的方法,判断入参是否在Set集合中,true放行,false拦截
* ---hasAnyAuthority:可以传入多个权限,只有用户有其中任意一个权限都可以访问对应资源
* ---hasRole:要求有对应的角色才可以访问,但是他内部会把我们传入的参数前面拼接:ROLE_ 后在比较。所以我们定义用户权限也要加这个前缀:ROLE_
* ---hasAnyRole:可以传入多个角色,有任意一个角色就可以访问资源
*
* @return
*/
@GetMapping("hello")
//加权限
// @PreAuthorize("hasAuthority('admin')")
// @PreAuthorize("hasAnyAuthority('admin','test')")
// @PreAuthorize("hasRole('admin')")
@PreAuthorize("hasAnyRole('admin','test')")
public ReturnT<String> hello() {
return ReturnT.success("博客地址:https://www.cnblogs.com/chenyanbin/");
}
}

项目源码

链接: https://pan.baidu.com/s/1h6NFpZ7HC9DY8s820hctTQ?pwd=h8um 提取码: h8um 

SpringBoot 2.5.5整合SpringSecurity+JWT的更多相关文章

  1. java框架之SpringBoot(15)-安全及整合SpringSecurity

    SpringSecurity介绍 Spring Security 是针对 Spring 项目的安全框架,也是 Spring Boot 底层安全模块默认的技术选型.它可以实现强大的 Web 安全控制.对 ...

  2. (七) SpringBoot起飞之路-整合SpringSecurity(Mybatis、JDBC、内存)

    兴趣的朋友可以去了解一下前五篇,你的赞就是对我最大的支持,感谢大家! (一) SpringBoot起飞之路-HelloWorld (二) SpringBoot起飞之路-入门原理分析 (三) Sprin ...

  3. 【SpringBoot】Springboot2.x整合SpringSecurity

    一.Spring Security是什么?有什么作用(核心作用)?以及如何阅读本篇文章 1.是什么 Spring Security是Spring家族的一个强大的安全框架,与Springboot整合的比 ...

  4. 厉害!我带的实习生仅用四步就整合好SpringSecurity+JWT实现登录认证!

    小二是新来的实习生,作为技术 leader,我还是很负责任的,有什么锅都想甩给他,啊,不,一不小心怎么把心里话全说出来了呢?重来! 小二是新来的实习生,作为技术 leader,我还是很负责任的,有什么 ...

  5. (八) SpringBoot起飞之路-整合Shiro详细教程(MyBatis、Thymeleaf)

    兴趣的朋友可以去了解一下前几篇,你的赞就是对我最大的支持,感谢大家! (一) SpringBoot起飞之路-HelloWorld (二) SpringBoot起飞之路-入门原理分析 (三) Sprin ...

  6. (九) SpringBoot起飞之路-整合/集成Swagger 2 And 3

    兴趣的朋友可以去了解一下其他几篇,你的赞就是对我最大的支持,感谢大家! (一) SpringBoot起飞之路-HelloWorld (二) SpringBoot起飞之路-入门原理分析 (三) Spri ...

  7. SpringBoot整合SpringSecurity实现JWT认证

    目录 前言 目录 1.创建SpringBoot工程 2.导入SpringSecurity与JWT的相关依赖 3.定义SpringSecurity需要的基础处理类 4. 构建JWT token工具类 5 ...

  8. SpringBoot整合SpringSecurity示例实现前后分离权限注解

    SpringBoot 整合SpringSecurity示例实现前后分离权限注解+JWT登录认证 作者:Sans_ juejin.im/post/5da82f066fb9a04e2a73daec 一.说 ...

  9. SpringBoot 整合 SpringSecurity 梳理

    文档 Spring Security Reference SpringBoot+SpringSecurity+jwt整合及初体验 JSON Web Token 入门教程 - 阮一峰 JWT 官网 Sp ...

  10. boke练习: springboot整合springSecurity出现的问题,传递csrf

    boke练习: springboot整合springSecurity出现的问题,传递csrf freemarker模板 在html页面中加入: <input name="_csrf&q ...

随机推荐

  1. hadoop部署2

    完全分布式部署介绍 学习目标 完全分部式是真正利用多台Linux主机来进行部署Hadoop,对Linux机器集群进行规划,使得Hadoop各个模块分别 部署在不同的多台机器上. 能够了解完全分布式部署 ...

  2. 【PB案例学习笔记】-02 目录浏览器

    写在前面 这是PB案例学习笔记系列文章的第二篇,该系列文章适合具有一定PB基础的读者, 通过一个个由浅入深的编程实战案例学习,提高编程技巧,以保证小伙伴们能应付公司的各种开发需求. 文章中设计到的源码 ...

  3. 平衡树 Treap & Splay [学习笔记]

    平衡树 \(\tt{Treap}\) & \(\tt{Splay}\) 壹.单旋 \(\tt{Treap}\) 首先了解 \(\tt{BST}\) 非常好用的东西,但是数据可以把它卡成一条链 ...

  4. Swoole 实践篇之结合 WebRTC 实现音视频实时通信方案

    原文首发链接:Swoole 实践篇之结合 WebRTC 实现音视频实时通信方案 大家好,我是码农先森. 引言 这次实现音视频实时通信的方案是基于 WebRTC 技术的,它是一种点对点的通信技术,通过浏 ...

  5. Flutter(一):MAC的Flutter安装指南

    官网地址 官网: https://flutter.dev Github: https://github.com/flutter/flutter Git的核心分支包括master.dev.stable. ...

  6. Kubernetes1.16安装[kubadm方式]

    Kubernetes 安装手册(非高可用版) 集群信息 1. 节点规划 部署k8s集群的节点按照用途可以划分为如下2类角色: master:集群的master节点,集群的初始化节点,基础配置不低于2C ...

  7. windows隐藏文件如何查看

    1.组织 2.查看 3.显示隐藏文件

  8. IDEA的安装、激活(到25年2月)&汉化

    1,在官网下载IDEA软件,官网 2,下载之后,双击安装包,然后一直点击next即可. (中间可以按照自己的要求设置安装目录) 3,快捷方式和java打钩 4,点击install即可进行安装,时间有一 ...

  9. XML文档定义的几种形式和本质区别

    XML文档定义的形式 两种定义形式:DTD.Schema DTD:数据类型定义(Data Type Definition),用以描述XML文档的文档结构,是早期的XML文档定义形式. Schema:其 ...

  10. Big Exponential Addition

    Big Exponential Addition 给定一非负整数n计算2^n的值,一般而言把 2 乘上 n 次,就能得到答案.然而,当n特别大时,2^n要一次次地乘2可能稍嫌太慢,面对此一巨大问题利用 ...