LEEYANGY

LEEYANGY 关注TA

拼搏百天,我要上湖工大!

LEEYANGY

LEEYANGY

关注TA

拼搏百天,我要上湖工大!

  •  Wuhan/HuBei
  • 学生
  • 写了322,476字

该文章投稿至Nemo社区   Java  板块 复制链接


Springboot3 + SpringSecurity6.1 + jwt 实现授权认证

发布于 2023/07/17 03:53 4,690浏览 0回复 53,866

我只能保证 使用security版本6.1.0-6.1.1

参考代码(对我有帮助的):yushuishu/springsecurity-examples: SpringSecurity6.x + Redis + Token (github.com)

security版本之间一些方法有些变化,比如说 and,在6.0.6?(大概)之后被放弃了,所以代码仅供参考

我的代码是在我原先博客项目上的,升级到了springboot3.0后,需要对其进行修改和适配,以下我抽取出来的代码,也许会对看到这篇文章的读者有帮助。

之前的流程还是没啥变化,该篇博客不会完全向你说明这个接口为什么要这么用,默认你已经有了前面的基础,我只分享我的代码。

博主水平有限,请不要提问与该篇博客主题外的错误。

项目大概框架如下:


数据库用户表,角色表取自若依后台管理,使用mp代码生成器生成实体类entity/pojo,控制器controller,业务层service,resource/mappres/**.xml,使用最新mybatisplus3.5.3对mysql数据库进行操作,pom依赖中自行补全



pom依赖

     <properties>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<springboot.version>3.1.0</springboot.version>
<mybatis.version>2.1.1</mybatis.version>
<lombok.version>1.18.10</lombok.version>
<junit.version>4.12</junit.version>
<mysql.version>8.0.32</mysql.version>
<mybatiplus.version>3.5.3</mybatiplus.version>
<jjwt.version>0.9.1</jjwt.version>
<fastjson.version>1.2.83</fastjson.version>
<hutools.version>5.8.11</hutools.version>
<lombok.version>1.18.26</lombok.version>
<apache.commons.version>3.5</apache.commons.version>
<velocity-engine-core.version>2.2</velocity-engine-core.version>
<!-- <maven.test.skip>true</maven.test.skip>-->
<javax-xml-bind.version>2.3.1</javax-xml-bind.version>
</properties>

<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api -->
<!-- SpringBoot3.0属于高版本!生成jwt需要在单独指定jaxb-api版本否则会报错 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${javax-xml-bind.version}</version>
</dependency>


resource中需要配置mysql数据库,redis数据库(参考)

application.yml

jackson:
locale: zh_CN
serialization:
# 格式化输出
indent-output: false
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss.SS
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html

mybatis-plus:
type-aliases-package: xyz.leeyangy.blog.modular.system.entity
mapper-locations: classpath:/mapper/*Mapper.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0

# springdoc-openapi项目配置
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
version: 1.0
api-docs:
path: /v3/api-docs
group-configs:
- group: 'default'
paths-to-match: '/**'
packages-to-scan: xyz.leeyangy.blog
# knife4j的增强配置,不需要增强可以不配
knife4j:
enable: true
setting:
language: zh_cn

server:
port: 65530

spring:
datasource:
url: jdbc:mysql://localhost:3306/new_blog?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: null
password: null
data:
redis:
password: null
port: 6379
host: null
database: 0


接下来是代码,代码片段中的TODO就是需要你自己修改的地方(阅读别人的代码也是件折磨人的事,由于是我的私人项目代码,暂时不会公开所有源代码,如果你有需要,请按照上面的结构图,自己建包)

springboot启动类

@SpringBootApplication
@MapperScan("xyz.leeyangy.blog.modular.system.mapper")
public class MyBlogApplication {

public static void main(String[] args) {
SpringApplication.run(MyBlogApplication.class, args);
}

}



SecurityConfig.class 配置文件

//  xyz.leeyangy.blog.modular.framework.config.SecurityConfig
package xyz.leeyangy.blog.modular.framework.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import xyz.leeyangy.blog.modular.framework.config.token.LocalLoginToken;
import xyz.leeyangy.blog.modular.framework.filter.JwtAuthenticationTokenFilter;
import xyz.leeyangy.blog.modular.framework.handler.AccessDeniedHandleImpl;
import xyz.leeyangy.blog.modular.framework.handler.AuthenticationEntryPointImpl;
import xyz.leeyangy.blog.modular.system.entity.pojo.User;
import xyz.leeyangy.blog.modular.system.entity.vo.LoginUserVo;
import xyz.leeyangy.blog.modular.system.mapper.UserMapper;
import xyz.leeyangy.blog.modular.system.service.UserService;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Objects;

/**
* @Author liyangyang
* @Date: 2023/05/18 14:38
* @Package xyz.leeyangy.blog.config
* @Version 1.0
* @Description:
*/

@Slf4j
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

@Resource
private final UserMapper userMapper;

/**
* @Param: []
* @return: org.springframework.security.crypto.password.PasswordEncoder
* @Author: liyangyang
* @Date: 2023/7/12 3:20
* @Description: 加密
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

/**
* @Param: []
* @return: org.springframework.security.crypto.password.NoOpPasswordEncoder
* @Author: liyangyang
* @Date: 2023/7/10 0:16
* @Description: 不需要加密
*/
// @Bean
// public static NoOpPasswordEncoder passwordEncoder() {
// return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
// }
// @Bean
// public AuthenticationManager authenticationManagerBean() {
// return authenticationManagerBean();
// }

/*
* 自定义拦截器
*/
private final AuthenticationEntryPointImpl authenticationEntryPoint;

/*
* 自定义拦截器
*/
private final AccessDeniedHandleImpl accessDeniedHandler;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// f.loginProcessingUrl("/api/v1/user/login").permitAll();
http
.authorizeHttpRequests(
auth -> {
auth.requestMatchers("/**").permitAll();

}
)
.formLogin(f -> {
// 自定义登录接口
// f.loginProcessingUrl("/api/v1/user/logins").permitAll().usernameParameter("userName").passwordParameter("password");
f.failureUrl("/test");
})
.sessionManagement(s -> {
// 基于token,所以不需要session
s.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
})
// 添加自定义认证过滤器,jwt拦截器
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)

// 添加自定义失败处理
.exceptionHandling(e -> {
e.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}

);
// 配置csrf ,configuration csrf
http.csrf(c->{
c.disable();
});
return http.build();
}

@Bean
public UserDetailsService localUserDetailsService() {
return username -> {
log.info("【LocalUserDetailsServiceImpl 认证】执行loadUserByUsername() 方法,获取账号:" + username);
// 根据用户名查询用户是否存在
// User findUserByName =
// new LambdaQueryChainWrapper<User>(userMapper).eq(User::getUserName, username).one();
User findUserByName = userMapper.selectOne(new QueryWrapper<User>().eq("user_name",username));
log.info("查询数据之后的代码执行了吗?");
if (Objects.isNull(findUserByName)) {
log.info("未知错误发生了");
throw new UsernameNotFoundException("用户名或密码错误!");
}
// TODO 角色,权限 还需修改
// 封装用户信息
LoginUserVo loginUserVo = new LoginUserVo();
loginUserVo.setUser(findUserByName);
// 获取用户权限
ArrayList<String> list = new ArrayList<>();
list.add("test");
log.info("添加了权限");
loginUserVo.setPermissions(list);

// 设置用户角色
// 查询角色业务操作

return loginUserVo;
};
}

@Bean
public AuthenticationManager authenticationManager() {
log.info("【AuthenticationManagerConfig】注册bean:authenticationManager");
return new ProviderManager(localDaoAuthenticationProvider());
}

@Bean
public AuthenticationProvider localDaoAuthenticationProvider() {
return new AuthenticationProvider() {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

// 获取 账号、密码
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
log.info("【LocalDaoAuthenticationProvider 认证】执行authenticate()方法,获取账号:" + username);
log.info("【LocalDaoAuthenticationProvider 认证】执行authenticate()方法,获取密码:" + password);
// 验证用户是否存在
LoginUserVo loginUserVo = (LoginUserVo) localUserDetailsService().loadUserByUsername(username);

// 校验密码正确性
if (!passwordEncoder().matches(password, loginUserVo.getUser().getPassword())) {
log.info("【LocalDaoAuthenticationProvider 认证】执行authenticate()方法,账号:{} 密码错误", password);
throw new BadCredentialsException("用户名或密码不正确");
}

return new LocalLoginToken(loginUserVo, password, loginUserVo.getAuthorities());
}

@Override
public boolean supports(Class<?> authentication) {
return true;
}
};
}


}


工具类:

jwt加密和解密工具

//  xyz.leeyangy.blog.modular.framework.utils.jwt.JwtUtil
package xyz.leeyangy.blog.modular.framework.utils.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

/**
* @Author liyangyang
* @Date: 2023/02/24 14:16
* @Package xyz.leeyangy.utils
* @Version 1.0
* @Description: JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 60 * 60 * 1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "king";

public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}

/**
* 生成jtw
*
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
// 设置过期时间
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}

/**
* 生成jtw
*
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
// 设置过期时间
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());
return builder.compact();
}

/**
* 创建token
*
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
// 设置过期时间
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);
return builder.compact();
}

private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("king") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}

/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}

/**
* 生成加密后的秘钥 secretKey
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
}

public static void main(String[] args) throws Exception {
String jwt = createJWT("123456");
System.out.println(jwt);
Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ZThhMDVjNzE1YmY0OGQ2ODE2MGY2NDIyZjZmOTJkNSIsInN1YiI6IjEyMzQ1NiIsImlzcyI6Inp3cCIsImlhdCI6MTY4OTEwMDU4NCwiZXhwIjoxNjg5MTA0MTg0fQ.l64F_OAth5QgGpMYUj3sVHGpm34XJEemwf2bNIoDm_M");
System.out.println(claims);
System.out.println(claims.getSubject());
}

}


redis:

FastJsonRedisSerializer.class

//xyz.leeyangy.blog.modular.framework.utils.redis.FastJsonRedisSerializer
package xyz.leeyangy.blog.modular.framework.utils.redis;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;

/**
* @Author liyangyang
* @Date: 2023/02/24 14:20
* @Package xyz.leeyangy.utils.jwt.redis
* @Version 1.0
* @Description: Redis使用FastJson序列化
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

private Class<T> clazz;

static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}

public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}

@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}

@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);

return JSON.parseObject(str, clazz);
}


protected JavaType getJavaType(Class<?> clazz) {
return TypeFactory.defaultInstance().constructType(clazz);
}
}

RedisCache.class 

//xyz.leeyangy.blog.modular.framework.utils.redis.RedisCache
package xyz.leeyangy.blog.modular.framework.utils.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
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.util.*;
import java.util.concurrent.TimeUnit;

/**
* @Author liyangyang
* @Date: 2023/02/24 14:22
* @Package xyz.leeyangy.utils.redis
* @Version 1.0
* @Description: Redis缓存工具类
*/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;

/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}

/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}

/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}

/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}

/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}

/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}

/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}

/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}

/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}

/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}

/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}

/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}

/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}

/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}

/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}

/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey) {
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}

/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}

/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}

}

RedisConfig.class 

//xyz.leeyangy.blog.modular.framework.utils.redis.RedisConfig
package xyz.leeyangy.blog.modular.framework.utils.redis;

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
* @Author liyangyang
* @Date: 2023/02/24 14:21
* @Package xyz.leeyangy.utils.redis
* @Version 1.0
* @Description: RedisConfig配置类
*/
public class RedisConfig {
@Bean
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);

FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);

// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);

// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);

template.afterPropertiesSet();
return template;
}
}


响应数据包装

response/ResponseResult.class

//xyz.leeyangy.blog.modular.framework.response.ResponseResult
package xyz.leeyangy.blog.modular.framework.response;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
* @Author liyangyang
* @Date: 2023/02/24 14:16
* @Package xyz.leeyangy.respone
* @Version 1.0
* @Description:
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {
/**
* 状态码
*/
private Integer code;
/**
* 提示信息,如果有错误时,前端可以获取该字段进行提示
*/
private String message;
/**
* 查询到的结果数据,
*/
private T data;

public ResponseResult(Integer code, String message) {
this.code = code;
this.message = message;
}

public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public String getMsg() {
return message;
}

public void setMsg(String message) {
this.message = message;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

public ResponseResult(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
}




实体类相关的对象

entity/dto/LoginDto.class

//xyz.leeyangy.blog.modular.system.entity.dto.LoginDto
package xyz.leeyangy.blog.modular.system.entity.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @Author liyangyang
* @Date: 2023/07/12 4:40
* @Package xyz.leeyangy.blog.modular.system.dto
* @Version 1.0
* @Description:
*/

public class LoginDto {
/**
* 用户账号
*/
private String userName;

/**
* 用户邮箱
*/
private String email;

/**
* 密码
*/
private String password;

public LoginDto() {
}

public LoginDto(String userName, String email, String password) {
this.userName = userName;
this.email = email;
this.password = password;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "LoginDto{" +
"userName='" + userName + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
'}';
}
}


pojo/User.class

//xyz.leeyangy.blog.modular.system.entity.pojo.User
package xyz.leeyangy.blog.modular.system.entity.pojo;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import xyz.leeyangy.blog.modular.framework.mybatisplus.base.BaseUserTimeEntity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
* <p>
* 用户信息表
* </p>
*
* @author leeyangy
* @since 2023-05-20
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_user")
public class User extends BaseUserTimeEntity {

private static final long serialVersionUID = 1L;

/**
* 用户ID
*/
@TableId(value = "user_id", type = IdType.AUTO)
private Long userId;

/**
* 部门ID
*/
private Long deptId;

/**
* 用户账号
*/
private String userName;

/**
* 用户昵称
*/
private String nickName;

/**
* 用户类型(00系统用户)
*/
private String userType;

/**
* 用户邮箱
*/
private String email;

/**
* 手机号码
*/
private String phonenumber;

/**
* 用户性别(0男 1女 2未知)
*/
private String sex;

/**
* 头像地址
*/
private String avatar;

/**
* 密码
*/
private String password;

/**
* 帐号状态(0正常 1停用)
*/
private String status;

/**
* 删除标志(0代表存在 2代表删除)
*/
private String delFlag;

/**
* 最后登录IP
*/
private String loginIp;

/**
* 最后登录时间
*/
private Date loginDate;

/**
* 备注
*/
private String remark;


}



Vo/LoginUserVo.class

//xyz.leeyangy.blog.modular.system.entity.vo.LoginUserVo
package xyz.leeyangy.blog.modular.system.entity.vo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import xyz.leeyangy.blog.modular.system.entity.pojo.User;

import java.io.Serial;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
* @Author liyangyang
* @Date: 2023/07/12 2:56
* @Package xyz.leeyangy.blog.modular.system.entity
* @Version 1.0
* @Description:
*/

@Setter
@Getter
@ToString
public class LoginUserVo implements UserDetails {

@Serial
private static final long serialVersionUID = -7850778107226817897L;

/**
* 用户信息
*/
private User user;

/**
* 用户权限
*/
private List<String> permissions;

/**
* 用户角色
*/
private List<RoleVo> roleList;

/**
* @Param: []
* @return:
* @Author: liyangyang
* @Date: 2023/7/17 0:32
* @Description: NoArgs
*/
public LoginUserVo() {
}

/**
* @Param: [user, permissions]
* @return:
* @Author: liyangyang
* @Date: 2023/7/17 0:33
* @Description: user,permissions
*/
public LoginUserVo(User user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}

/**
* @Param: [user, permissions, roleList]
* @return:
* @Author: liyangyang
* @Date: 2023/7/17 0:35
* @Description: user, permissions, roleList
*/
public LoginUserVo(User user, List<String> permissions, List<RoleVo> roleList) {
this.user = user;
this.permissions = permissions;
this.roleList = roleList;
}

@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
return permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}

@Override
public String getPassword() {
return user.getPassword();
}

@Override
public String getUsername() {
return user.getUserName();
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

}







控制器:

UserController.class

//xyz.leeyangy.blog.modular.system.controller.UserController
package xyz.leeyangy.blog.modular.system.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import xyz.leeyangy.blog.modular.framework.response.ResponseResult;
import xyz.leeyangy.blog.modular.system.entity.dto.LoginDto;
import xyz.leeyangy.blog.modular.system.service.UserService;

/**
* @Author liyangyang
* @Date: 2023/07/12 4:16
* @Package xyz.leeyangy.blog.modular.system.controller
* @Version 1.0
* @Description:
*/

@Slf4j
@RestController
@Tag(name = "用户接口")
@RequestMapping("/api/v1/user")
@RequiredArgsConstructor
public class UserController {

private final UserService userService;

@PostMapping("/login")
@Operation(summary = "用户登录")
public ResponseResult login(@RequestBody LoginDto loginDto, HttpServletResponse response) {
log.info("请求了用户登录接口asasdasdasda");
log.info("{}", loginDto);
return userService.login(loginDto,response);
}

}

TestContoller.class

//xyz.leeyangy.blog.modular.system.controller.TestController
package xyz.leeyangy.blog.modular.system.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.leeyangy.blog.modular.framework.response.ResponseData;

/**
* @Author liyangyang
* @Date: 2023/07/12 1:57
* @Package xyz.leeyangy.blog.modular.system.controller
* @Version 1.0
* @Description:
*/

@RestController
public class TestController {

@GetMapping("/test")
public ResponseData test(){
return ResponseData.success(200,"成功","成功");
}

@PostMapping("/tests")
public ResponseData tests(){
return ResponseData.success(200,"成功","成功");
}
}




业务层:

UserService.class

//xyz.leeyangy.blog.modular.system.service.UserService
package xyz.leeyangy.blog.modular.system.service;

import xyz.leeyangy.blog.modular.framework.response.ResponseResult;
import xyz.leeyangy.blog.modular.system.entity.dto.LoginDto;
import xyz.leeyangy.blog.modular.system.entity.pojo.User;
import com.baomidou.mybatisplus.extension.service.IService;

import jakarta.servlet.http.HttpServletResponse;

/**
* <p>
* 用户信息表 服务类
* </p>
*
* @author leeyangy
* @since 2023-05-20
*/
public interface UserService extends IService<User> {

ResponseResult login(LoginDto loginDto, HttpServletResponse response);
}

UserServiceImpl.class

//xyz.leeyangy.blog.modular.system.service.impl.UserServiceImpl
package xyz.leeyangy.blog.modular.system.service.impl;

import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Transactional;
import xyz.leeyangy.blog.modular.framework.config.token.LocalLoginToken;
import xyz.leeyangy.blog.modular.framework.constants.AuthHeadConstant;
import xyz.leeyangy.blog.modular.framework.response.ResponseResult;
import xyz.leeyangy.blog.modular.framework.utils.jwt.JwtUtil;
import xyz.leeyangy.blog.modular.framework.utils.redis.RedisCache;
import xyz.leeyangy.blog.modular.system.entity.dto.LoginDto;
import xyz.leeyangy.blog.modular.system.entity.vo.LoginUserVo;
import xyz.leeyangy.blog.modular.system.entity.pojo.User;
import xyz.leeyangy.blog.modular.system.mapper.UserMapper;
import xyz.leeyangy.blog.modular.system.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Objects;

/**
* <p>
* 用户信息表 服务实现类
* </p>
*
* @author leeyangy
* @since 2023-05-20
*/

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = RuntimeException.class)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
//public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

private final AuthenticationManager authenticationManager;

private final RedisCache redisCache;

// @Override
// public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// log.info("{}",username);
//// 根据用户名查询用户是否存在
// xyz.leeyangy.blog.modular.system.entity.pojo.User findUserByName =
// new LambdaQueryChainWrapper<xyz.leeyangy.blog.modular.system.entity.pojo.User>(baseMapper).eq(xyz.leeyangy.blog.modular.system.entity.pojo.User::getUserName, username).one();
// if (Objects.isNull(findUserByName)) throw new UsernameNotFoundException("用户名或密码错误!");
//
//// 获取用户权限
// ArrayList<String> list = new ArrayList<>();
// list.add("test");
// log.info("添加了权限");
// return new LoginUserVo(findUserByName, list);
// }

@Override
public ResponseResult login(LoginDto loginDto, HttpServletResponse response) {
log.info("impl中 {}", loginDto);
// 将用户密码交给security进行验证
// UsernamePasswordAuthenticationToken authenticationToken =
// new UsernamePasswordAuthenticationToken(loginDto.getUserName(), loginDto.getPassword());
// log.info("在userServiceImpl authenticationToken之后,查看 {} 信息\n",authenticationToken);
// 使用Authentication 进行用户认证
Authentication authenticate = authenticationManager.authenticate(new LocalLoginToken(loginDto.getUserName(), loginDto.getPassword()));
log.info("获取用户信息成功");
// 不通过给出提示
if (Objects.isNull(authenticate)) {
log.info("登录失败");
throw new RuntimeException("登录失败");
}

// 通过生成jwt
LoginUserVo loginUserVo = (LoginUserVo) authenticate.getPrincipal();
log.info("登录成功");
String jwt = JwtUtil.createJWT(loginUserVo.getUser().getUserName());
// 将用户信息存入redis中
redisCache.setCacheObject("login: "+loginUserVo.getUser().getUserName(),loginUserVo);
response.setHeader(AuthHeadConstant.Authorization,jwt);
return new ResponseResult(200, "登录成功", loginUserVo);
}
}


认证授权处理

handler/AccessDeniedHandleImpl.class

package xyz.leeyangy.blog.modular.framework.handler;

import com.alibaba.fastjson.JSON;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import xyz.leeyangy.blog.modular.framework.response.ResponseResult;
import xyz.leeyangy.blog.modular.framework.utils.web.WebUtil;

import java.io.IOException;

/**
* @Author liyangyang
* @Date: 2023/04/03 0:59
* @Package xyz.leeyangy.handle
* @Version 1.0
* @Description: 授权失败处理
*/

@Component
public class AccessDeniedHandleImpl implements AccessDeniedHandler {
/**
* @param request
* @param response
* @param accessDeniedException
* @throws IOException
* @throws ServletException
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResponseResult result = new ResponseResult<>(HttpStatus.FORBIDDEN.value(), "您的权限不足,请开通会员");
String json = JSON.toJSONString(result);
// 处理异常
WebUtil.renderString(response,json);
}
}



package xyz.leeyangy.blog.modular.framework.handler;

import com.alibaba.fastjson.JSON;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import xyz.leeyangy.blog.modular.framework.response.ResponseResult;
import xyz.leeyangy.blog.modular.framework.utils.web.WebUtil;


import java.io.IOException;

/**
* @Author liyangyang
* @Date: 2023/04/03 0:52
* @Package xyz.leeyangy.handle
* @Version 1.0
* @Description: 自定义未授权处理
*/

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
/**
* @param request
* @param response
* @param authException
* @throws IOException
* @throws ServletException
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResponseResult result = new ResponseResult<>(HttpStatus.UNAUTHORIZED.value(),"用户认证失败,请登录");
String json = JSON.toJSONString(result);
// 处理异常
WebUtil.renderString(response,json);
}
}


jwt拦截器

filtter/JwtAuthenticationTokenFilter.class

//xyz.leeyangy.blog.modular.framework.filter.JwtAuthenticationTokenFilter
package xyz.leeyangy.blog.modular.framework.filter;

import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import xyz.leeyangy.blog.modular.framework.utils.jwt.JwtUtil;
import xyz.leeyangy.blog.modular.framework.utils.redis.RedisCache;
import xyz.leeyangy.blog.modular.system.entity.vo.LoginUserVo;

import javax.annotation.Resource;

import java.io.IOException;
import java.util.Objects;

/**
* @Author liyangyang
* @Date: 2023/02/24 21:57
* @Package xyz.leeyangy.filter
* @Version 1.0
* @Description:
*/

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

private final RedisCache redisCache;

/**
* @param request
* @param response
* @param filterChain
* @throws ServletException
* @throws IOException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
// 获取token
// String token = request.getHeader("token");
String token = request.getHeader("Authorization");
System.out.println("被拦截咯,在获取token之前,49行");
if (!StringUtils.hasText(token)) {
// 为空直接放行
filterChain.doFilter(request, response);
return;
}
// eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwMTY0N2E1MDU0ZGI0MDA3OTU4OWQxNTcxMTEzNDU2OSIsInN1YiI6ImxlZXlhbmd5IiwiaXNzIjoiendwIiwiaWF0IjoxNjg5NTMxOTAzLCJleHAiOjE2ODk1MzU1MDN9.JRIYo8Og9z5DTt6mJ28EwgxR6QzCLv4J49WlE9fprCM
log.info("被拦截咯,在获取token中间,59行,拦截token {}",token);
// 解析token
String userName;
try {
Claims claims = JwtUtil.parseJWT(token);
userName = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法");
}
log.info("解密token信息,{}",userName);
// 从redis中获取用户信息
String redisKey = "login: "+ userName;
// String redisKey = "loginUserVo.getUser().getUserName(): "+userName;
// 反序列化获取登录用户信息
LoginUserVo loginUser = redisCache.getCacheObject(redisKey);
if (Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
// 存入SecurityContext
// 获取权限信息封装
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
log.info("查看认证用户:{},查看用户权限信息:{}",authenticationToken,SecurityContextHolder.getContext().getAuthentication());
// 放行
filterChain.doFilter(request,response);
}
}



自定义UsernamePasswordAuthenticationToken

config/token/LocalLoginToken.class

package xyz.leeyangy.blog.modular.framework.config.token;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.io.Serial;
import java.util.Collection;

/**
* @Author liyangyang
* @Date: 2023/07/15 3:58
* @Package xyz.leeyangy.blog.modular.framework.config.token
* @Version 1.0
* @Description:
*/
public class LocalLoginToken extends UsernamePasswordAuthenticationToken {

@Serial
private static final long serialVersionUID = 8634771078835164272L;

public LocalLoginToken(Object principal, Object credentials) {
super(principal, credentials);
}

public LocalLoginToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}

}



demo演示:

接口:localhost:65530/api/v1/user/login

图中打码原因:遮蔽关键信息


接口:localhost:65530/test


由于我开放了所有接口,所以以上代码只能测试到携带token进行验证,携带了正确token,和不携带token都能访问test接口,如果携带错误token则会提示你的token是错的(非法token)



静态权限加载(程序在启动时候,就从数据库中获取对应的权限信息)

修改SecurityConfig.class

package xyz.leeyangy.blog.modular.framework.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import xyz.leeyangy.blog.modular.framework.config.token.LocalLoginToken;
import xyz.leeyangy.blog.modular.framework.constants.SecurityConstant;
import xyz.leeyangy.blog.modular.framework.filter.JwtAuthenticationTokenFilter;
import xyz.leeyangy.blog.modular.framework.handler.AccessDeniedHandleImpl;
import xyz.leeyangy.blog.modular.framework.handler.AuthenticationEntryPointImpl;
import xyz.leeyangy.blog.modular.system.entity.pojo.Menu;
import xyz.leeyangy.blog.modular.system.entity.pojo.User;
import xyz.leeyangy.blog.modular.system.entity.vo.LoginUserVo;
import xyz.leeyangy.blog.modular.system.mapper.MenuMapper;
import xyz.leeyangy.blog.modular.system.mapper.UserMapper;
import xyz.leeyangy.blog.modular.system.service.UserService;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
* @Author liyangyang
* @Date: 2023/05/18 14:38
* @Package xyz.leeyangy.blog.config
* @Version 1.0
* @Description:
*/

@Slf4j
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

@Resource
private final UserMapper userMapper;

@Resource
private final MenuMapper menuMapper;

/**
* @Param: []
* @return: org.springframework.security.crypto.password.PasswordEncoder
* @Author: liyangyang
* @Date: 2023/7/12 3:20
* @Description: 加密
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

/**
* @Param: []
* @return: org.springframework.security.crypto.password.NoOpPasswordEncoder
* @Author: liyangyang
* @Date: 2023/7/10 0:16
* @Description: 不需要加密
*/
// @Bean
// public static NoOpPasswordEncoder passwordEncoder() {
// return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
// }
// @Bean
// public AuthenticationManager authenticationManagerBean() {
// return authenticationManagerBean();
// }

/*
* 自定义拦截器
*/
private final AuthenticationEntryPointImpl authenticationEntryPoint;

/*
* 自定义拦截器
*/
private final AccessDeniedHandleImpl accessDeniedHandler;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

List<Menu> menus = menuMapper.selectList(new QueryWrapper<Menu>());

menus.forEach(
menu -> {
try {
http.authorizeHttpRequests(auth -> {
auth.requestMatchers(menu.getPath()).hasAnyAuthority(menu.getPerms());
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
);

// f.loginProcessingUrl("/api/v1/user/login").permitAll();
http
.authorizeHttpRequests(
auth -> {
// 放开登录接口,拦截其它所有接口
auth.requestMatchers(SecurityConstant.SECURITY_URL).permitAll();
// .requestMatchers("/**").fullyAuthenticated();
}
)

.formLogin(f -> {
// 自定义登录接口
// f.loginProcessingUrl("/api/v1/user/logins").permitAll().usernameParameter("userName").passwordParameter("password");
})
.sessionManagement(s -> {
// 基于token,所以不需要session
s.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
})
// 添加自定义认证过滤器,jwt拦截器
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)

// 添加自定义失败处理
.exceptionHandling(e -> {
e.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}

);
// 配置csrf ,configuration csrf
http.csrf(c -> {
c
.disable();
});
return http.build();
}

@Bean
public UserDetailsService localUserDetailsService() {
return username -> {
log.info("【LocalUserDetailsServiceImpl 认证】执行loadUserByUsername() 方法,获取账号:" + username);
// 根据用户名查询用户是否存在
// User findUserByName =
// new LambdaQueryChainWrapper<User>(userMapper).eq(User::getUserName, username).one();
User findUserByName = userMapper.selectOne(new QueryWrapper<User>().eq("user_name", username));
if (Objects.isNull(findUserByName)) {
log.info("未知错误发生了");
throw new UsernameNotFoundException("用户名或密码错误!");
}
// TODO 角色,权限 还需修改
// 封装用户信息
LoginUserVo loginUserVo = new LoginUserVo();
loginUserVo.setUser(findUserByName);
// 获取用户权限
ArrayList<String> list = new ArrayList<>();
list.add("system:config:list");
list.add("system:notice:list");
loginUserVo.setPermissions(list);
// 设置用户角色
// 查询角色业务操作

return loginUserVo;
};
}

@Bean
public AuthenticationManager authenticationManager() {
log.info("【AuthenticationManagerConfig】注册bean:authenticationManager");
return new ProviderManager(localDaoAuthenticationProvider());
}

@Bean
public AuthenticationProvider localDaoAuthenticationProvider() {
return new AuthenticationProvider() {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

// 获取 账号、密码
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
log.info("【LocalDaoAuthenticationProvider 认证】执行authenticate()方法,获取账号:" + username);
log.info("【LocalDaoAuthenticationProvider 认证】执行authenticate()方法,获取密码:" + password);
// 验证用户是否存在
LoginUserVo loginUserVo = (LoginUserVo) localUserDetailsService().loadUserByUsername(username);

// 校验密码正确性
if (!passwordEncoder().matches(password, loginUserVo.getUser().getPassword())) {
log.info("【LocalDaoAuthenticationProvider 认证】执行authenticate()方法,账号:{} 密码错误", password);
throw new BadCredentialsException("用户名或密码不正确");
}

return new LocalLoginToken(loginUserVo, password, loginUserVo.getAuthorities());
}

@Override
public boolean supports(Class<?> authentication) {
return LocalLoginToken.class.isAssignableFrom(authentication);
}
};
}


}

 

//  在登录的时候,给予对应的死权限(实际是动态查询)

ArrayList<String> list = new ArrayList<>();
list.add("system:config:list");
list.add("system:notice:list");



修改TestController.class

@RestController
//@RequestMapping("/api/v1")
public class TestController {

@PostAuthorize("hasAuthority('system:config:list')")
@GetMapping("/config")
public ResponseData test(){
return ResponseData.success(200,"配置信息成功","成功");
}

@PostAuthorize("hasAuthority('system:notice:list')")
@GetMapping("/notice")
public ResponseData tests(){
return ResponseData.success(200,"通知成功","成功");
}
}


测试接口1:localhost:65530/notices


测试接口2:localhost:65530/notice


至此已实现部分核心功能。


动态查询





点赞(0)

下一个文章:springcloud课程设计

点了个评