Spring Boot 集成 spring security 01

一、导入依赖(pom.xml)

<?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><groupId>com.wgz</groupId><artifactId>wgz-security-auth</artifactId><version>0.0.1-SNAPSHOT</version><name>wgz-security-auth</name><description>账号统一认证</description><properties><java.version>8</java.version><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><spring-boot.version>2.7.12</spring-boot.version><spring-cloud.version>2021.0.7</spring-cloud.version><spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><lombok.version>1.18.24</lombok.version><mysql.version>8.0.31</mysql.version><mybatis-plus.version>3.5.3.1</mybatis-plus.version><pagehelper.version>1.4.6</pagehelper.version><knife4j.version>4.1.0</knife4j.version><fastjson.version>2.0.23</fastjson.version><redisson-spring.version>3.13.6</redisson-spring.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--mybatis分页插件--><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId></dependency><!--参数验证依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!--thymeleaf模板引擎--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId></dependency><!--        热部署插件--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><!--使用redisson作为分布式锁--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId></dependency><!-- mysql数据库驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>${mysql.version}</version></dependency><!--mybati-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>${mybatis-plus.version}</version></dependency><!--引入spring boot 集成的PageHelper分页插件--><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>${pagehelper.version}</version></dependency><!--lombok依赖--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--添加fastjson依赖--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><!-- 文档工具 knife4j-openapi3--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-spring-boot-starter</artifactId><version>${knife4j.version}</version></dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency></dependencies><dependencyManagement><dependencies><!-- spring boot 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><!-- spring cloud 依赖 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><!-- spring cloud alibaba 依赖 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!--使用redisson作为分布式锁--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>${redisson-spring.version}</version></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-data-27</artifactId><version>${redisson-spring.version}</version></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.9.0</version><configuration><source>${java.version}</source><target>${java.version}</target><encoding>${project.build.sourceEncoding}</encoding><annotationProcessorPaths><path><groupId>com.github.therapi</groupId><artifactId>therapi-runtime-javadoc-scribe</artifactId><version>0.15.0</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></path><path><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><version>${spring-boot.version}</version></path></annotationProcessorPaths></configuration></plugin></plugins><resources><resource><directory>src/main/resources</directory><!-- 关闭过滤 --><filtering>false</filtering></resource><resource><directory>src/main/resources</directory><!-- 引入所有 匹配文件进行过滤 --><includes><include>application*</include><include>bootstrap*</include><include>banner*</include></includes><!-- 启用过滤 即该资源中的变量将会被过滤器中的值替换 --><filtering>true</filtering></resource></resources></build><profiles><profile><id>dev</id><properties><!-- 环境标识,需要与配置文件的名称相对应 --><profiles.active>dev</profiles.active><logging.level>info</logging.level></properties></profile><profile><id>test</id><properties><!-- 环境标识,需要与配置文件的名称相对应 --><profiles.active>test</profiles.active><logging.level>debug</logging.level></properties></profile><profile><id>uat</id><properties><profiles.active>uat</profiles.active><logging.level>debug</logging.level></properties></profile><profile><id>prod</id><properties><profiles.active>prod</profiles.active><logging.level>warn</logging.level></properties></profile></profiles>
</project>

二、application配置

1、application.yml

spring:profiles:active: @profiles.active@main:allow-circular-references: trueweb:resources:static-locations: classpath:/static/thymeleaf:cache: falseprefix: classpath:/templates/security:user:name: jingpassword: 1234

2、application-dev.yml

server:port: 3001spring:#数据源datasource:mysql:driverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://127.0.0.1:3306/wgz-security-auth?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&autoReconnect=true&allowMultiQueries=truetype: com.zaxxer.hikari.HikariDataSourceusername: rootpassword: 123456initialSize: 10maxActive: 20minIdle: 1maximumPoolSize: 20autoCommit: truepoolName: HikariPool_mysqlmaxLifetime: 600000connectionTestQuery: SELECT 1# redisredis:database: 2host: 127.0.0.1port: 6379username:password: 123456timeout: -1lettuce:pool:max-active: 8  # 连接池最大连接数(使用负值表示没有限制) 默认 8max-wait: -1   # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1max-idle: 8    # 连接池中的最大空闲连接 默认 8min-idle: 0    # 连接池中的最小空闲连接 默认 0#knife4j文档配置
knife4j:enable: true  #是否开启Swaggerproduction: false #当前环境是否为生产环境basic:enable: true  #进入界面是否需要账号密码username: adminpassword: admin123131

三、security config配置

import com.wgz.auth.Filter.TokenAuthenticationFilter;
import com.wgz.auth.Filter.TokenLoginFilter;
import com.wgz.auth.service.impl.UserDetailServiceImpl;
import com.wgz.auth.util.CustomPwdEncoderUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
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.web.authentication.UsernamePasswordAuthenticationFilter;/*** spring security 配置* 1.AuthenticationManager 认证管理器bean对象* 2.configure HttpSecurity 需要开启和绕过防护的接口 + 过滤器注册 + session管理* 3.configure AuthenticationManagerBuilder 认证管理器使用的资源* 4.configure WebSecurity 不需要拦截的路径*/
@Slf4j
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 自定义查询用户信息类(默认在providerService查询)*/private final UserDetailServiceImpl userService;/*** 自定义加密工具类*/private final CustomPwdEncoderUtil customPwdEncoder;/*** redis存储对象*/private final RedisTemplate<String, String> redisTemplate;/*** 配置认证管理器bean对象*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** 防护配置:配置需要开启和绕过防护的接口 + 过滤器注册 + session管理*/@Overrideprotected void configure(HttpSecurity http) throws Exception {//          //自定义登陆页面//        http//                .formLogin()//                //登陆页面设置(页面访问的地方)//                .loginPage("/login")//                //登陆访问路径(表单访问的地方[post])//                .loginProcessingUrl("/login/doLogin")//                //登陆成功后跳转路径(页面)//                .defaultSuccessUrl("/login/success").permitAll()//                .and()//                .authorizeRequests()//                //设置不需要认证的访问路径,可以直接访问//                .antMatchers("/", "/login/doLogin").permitAll();//配置认证信息:配置绕过的接口和注册过滤器(1.登录验证过滤器 + 2.token认证过滤器)http//关闭csrf.csrf().disable()//开启跨域以便前端调用接口.cors().and().authorizeRequests()//配置不需要认证的接口 如:登录接口.antMatchers("/login/doLogin").anonymous()//其他所有接口需要认证才能访问.anyRequest().authenticated().and()//添加token认证过滤器 方式一//                .addFilterBefore(new TokenAuthenticationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class)//添加登录过滤器.addFilter(new TokenLoginFilter(authenticationManagerBean(), redisTemplate))//添加token认证过滤器 方式二.addFilterAfter(new TokenAuthenticationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class);//配置session信息:禁用http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}/*** 配置认证管理器使用的资源:* 1.查询用户信息的类* 2.自定义加密器*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(customPwdEncoder);}/*** 配置不需要拦截的路径* 配置后将不再进入过滤器进行校验,直接请求到对应的资源地址* 如:包含/login/doLogin ,* 那在请求这个接口时,直接到达该接口地址,* 不再进入security过滤器*/@Overridepublic void configure(WebSecurity web) {web.ignoring().antMatchers("/favicon.ico","/swagger-resources/**","/webjars/**","/v2/**","/v3/**",//                "/login/**", //放开该地址后,请求将不再进入过滤器校验与认证,如:不使用过滤器的方式校验登录"/swagger-ui.html","/doc.html");}
}

四、redis配置

1、redis config

import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.annotation.EnableCaching;
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.*;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** redis配置*/
@EnableCaching
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig {private final RedisConnectionFactory factory;@Beanpublic RedisTemplate<String, Object> redisTemplate() {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());redisTemplate.setConnectionFactory(factory);return redisTemplate;}@Beanpublic HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForHash();}@Beanpublic ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {return redisTemplate.opsForValue();}@Beanpublic ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForList();}@Beanpublic SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForSet();}@Beanpublic ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForZSet();}
}

2、redisson config

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;/*** redisson配置*/
@Configuration
public class RedissonConfig {@Value("${spring.redis.host:127.0.0.1}")private String host;@Value("${spring.redis.port:6379}")private String port;@Value("${spring.redis.password}")private String password;@Value("${spring.redis.username:''}")private String username;@Value("${spring.redis.database:1}")private Integer database;@Beanpublic RedissonClient singleRedisClient() {Config config = new Config();SingleServerConfig singleServerConfig = config.useSingleServer();if (StringUtils.hasLength(password)) {singleServerConfig.setPassword(password);}if (StringUtils.hasLength(username)) {singleServerConfig.setUsername(username);}String url = "redis://" + host + ":" + port;singleServerConfig.setAddress(url);singleServerConfig.setDatabase(database);return Redisson.create(config);}
}

五、mysql config配置

import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.TransactionManager;import javax.sql.DataSource;
import java.util.*;/*** 数据源配置*/
@Configuration
//这里设置扫描dao接口了,启动类与dao接口就不用在配置mapper扫描注解
@MapperScan(basePackages = "com.wgz.auth.mapper", sqlSessionFactoryRef = "mySqlSessionFactory")
public class HikariMySqlConfig {/*** @ConfigurationProperties 读取yml中的配置参数映射成为一个对象*/@Bean(name = "mySqlDataSource")@ConfigurationProperties(prefix = "spring.datasource.mysql")public HikariDataSource mySqlDateSource() {return new HikariDataSource();}@Bean(name = "mySqlSessionFactory")public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mySqlDataSource") DataSource datasource) throws Exception {MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();bean.setDataSource(datasource);//-----多路径是扫描//配置xml扫描路径List<String> resourcePatterns = new ArrayList<>();resourcePatterns.add("classpath*:mapper/*.xml");resourcePatterns.add("classpath*:mapper/*/*.xml");Set<Resource> resources = new LinkedHashSet<>(resourcePatterns.size());for (String resourcePattern : resourcePatterns) {Resource[] resource = new PathMatchingResourcePatternResolver().getResources(resourcePattern);resources.addAll(Arrays.asList(resource));}Resource[] resourcesArr = new Resource[resources.size()];//mybatis扫描xml所在位置bean.setMapperLocations(resources.toArray(resourcesArr));//-----单路径是扫描//mybatis扫描xml所在位置//bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));return bean.getObject();}@Bean("mySqlSessionTemplate")public SqlSessionTemplate mysqlSqlSessionTemplate(@Qualifier("mySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}//不配值这个bean,@Transaction注解可能失效//使用时 @Transactional(value = "mySqlTransactionManager",rollbackFor = {Exception.class, RuntimeException.class})@Bean("mySqlTransactionManager")public TransactionManager mysqlTransactionManager() {return new JdbcTransactionManager(mySqlDateSource());}
}

六、Open Api文档配置

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** OpenApi 配置*/
@Configuration
public class OpenApiConfig {@Beanpublic OpenAPI customOpenAPI() {return new OpenAPI().info(new Info().title("认证中心").contact(new Contact().name("XX.XX").email("xxx@zz.com")).version("1.0").description("认证中心接口文档").license(new License().name("Apache 2.0")));}
}

七、AES加密工具

import org.springframework.util.DigestUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;import static com.wgz.auth.constant.Common.AES_SECRET_KEY;/*** AES 加密工具类*/
public class AesUtil {// 加密算法RSApublic static final String KEY_ALGORITHM = "AES";//编码方式public static final String CODE_TYPE = "UTF-8";//填充类型 AES/ECB/PKCS5Padding   AES/ECB/ISO10126Paddingpublic static final String AES_TYPE = "AES/ECB/PKCS5Padding";/*** 自定义内容加盐,生成AES秘钥*/public static String generateAESKey() {return DigestUtils.md5DigestAsHex(getSalt(6));}/*** 随机生成加盐类*/public static byte[] getSalt(int n) {char[] chars = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +"1234567890!@#$%^&*()_+").toCharArray();StringBuilder stringBuilder = new StringBuilder();SecureRandom random = new SecureRandom();for (int i = 0; i < n; i++) {stringBuilder.append(chars[random.nextInt(chars.length)]);}return stringBuilder.toString().getBytes();}/*** 加密** @param clearText 明文* @param aesKey    AES秘钥* @return 加密串*/public static String encryptAes(String clearText, String aesKey) {try {SecretKeySpec key = new SecretKeySpec(aesKey.getBytes(), KEY_ALGORITHM);Cipher cipher = Cipher.getInstance(AES_TYPE);cipher.init(Cipher.ENCRYPT_MODE, key);byte[] encryptedData = cipher.doFinal(clearText.getBytes(CODE_TYPE));return new BASE64Encoder().encode(encryptedData);} catch (Exception e) {throw new RuntimeException("加密失败", e);}}/*** 解密** @param encryptText 密文* @param aesKey      AES秘钥* @return 解密串*/public static String decryptAes(String encryptText, String aesKey) {try {byte[] byteMi = new BASE64Decoder().decodeBuffer(encryptText);SecretKeySpec key = new SecretKeySpec(aesKey.getBytes(), KEY_ALGORITHM);Cipher cipher = Cipher.getInstance(AES_TYPE);cipher.init(Cipher.DECRYPT_MODE, key);byte[] decryptedData = cipher.doFinal(byteMi);return new String(decryptedData, CODE_TYPE);} catch (Exception e) {throw new RuntimeException("解密失败", e);}}public static void main(String[] args) {//String aesKey = generateAESKey();String json = "admin123131";//加密System.out.println("字符串:" + json);String encrypt = encryptAes(json, AES_SECRET_KEY);System.out.println("加密后字符串:" + encrypt);//私钥解密System.out.println("解密后字符串:" + decryptAes(encrypt, AES_SECRET_KEY));}
}

八、自定义密码处理组件

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;import static com.wgz.auth.constant.Common.AES_SECRET_KEY;//一、自定义密码处理组件
@Component
public class CustomPwdEncoderUtil implements PasswordEncoder {@Overridepublic String encode(CharSequence rawPassword) {//进行MD5加密//MD5.encrypt(rawPassword.toString());return AesUtil.encryptAes(String.valueOf(rawPassword), AES_SECRET_KEY);}@Overridepublic boolean matches(CharSequence rawPassword, String encodePassword) {//判断是否相等//return encodePassword.equals(MD5.encrypt(rawPassword.toString()));return AesUtil.encryptAes(String.valueOf(rawPassword), AES_SECRET_KEY).equals(encodePassword);}
}

九、Response工具

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wgz.auth.constant.Result;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class ResponseUtil {public static void out(HttpServletResponse response, Result r) {ObjectMapper mapper = new ObjectMapper();response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);try {mapper.writeValue(response.getWriter(), r);} catch (IOException e) {e.printStackTrace();}}
}

十、common constant 常量

import java.time.Duration;public interface Common {/*** spring security 继承过滤器(UsernamePasswordAuthenticationFilter)验证方式:TokenLoginFilter*/String TOKEN_PREF_DEFAULT = "AUTH:TOKEN:DEFAULT:";/*** spring security 自定义接口验证方式:* com.wgz.auth.controller.LoginController#doLogin(com.wgz.auth.entity.UserLoginDto)*/String TOKEN_PREF_INTERFACE = "AUTH:TOKEN:INTERFACE:";/*** 登录过期时间*/Duration REDIS_TIME_OUT = Duration.ofHours(3);/*** AES密钥*/String AES_SECRET_KEY = "a57e3db23f9c89e606abe9b9b5fc7f30";
}

十一、自定义UserDetail类

import com.wgz.auth.entity.user.UserVo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;import java.util.Collection;/*** 自定义用户类(继承Security里的User类)*/
@Getter
@Setter
@ToString
@Schema(description = "自定义用户类: 继承Security里的User类")
public class CustomUser extends User {/*** 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象*/private UserVo userVo;public CustomUser(UserVo userVo, Collection<? extends GrantedAuthority> authorities) {//调用父类构造器初始化信息super(userVo.getUserName(), userVo.getPassword(), authorities);this.userVo = userVo;}
}

十二、登录过滤器配置

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wgz.auth.entity.user.UserLoginDto;
import com.wgz.auth.constant.ResponseEnum;
import com.wgz.auth.constant.Result;
import com.wgz.auth.handler.CustomException;
import com.wgz.auth.util.ResponseUtil;
import lombok.SneakyThrows;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;/*** token 登录过滤器:* 1.【只对】配置的登录路径进行拦截,* 2.拦截后再对提交的登录信息进行认证* 3.认证成功后颁发token,认证失败提示*/
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {private RedisTemplate<String, String> redisTemplate;/*** 初始化:资源准备*/public TokenLoginFilter(AuthenticationManager authenticationManager, RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;//设置/初始化  认证管理器this.setAuthenticationManager(authenticationManager);//取消 只针对post请求this.setPostOnly(false);//指定登录接口及提交方式,可以指定任意路径this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login/doLogin","POST"));}/*** 拦截提交的登录信息并进行认证*/@SneakyThrows@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {//        //1.request param body为form-data的方式(如:一般未处理的表单提交方式)//        UserLoginDto loginDto = new UserLoginDto();//        loginDto.setUsername(request.getParameter("username"));//        loginDto.setPassword(request.getParameter("password"));//2.request param body为json的方式UserLoginDto loginDto = new ObjectMapper().readValue(request.getInputStream(), UserLoginDto.class);//创建UsernamePasswordAuthenticationToken对象,封装用户名和密码,得到认证对象Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());//返回目标认证对象return this.getAuthenticationManager().authenticate(authenticationToken);}/*** 认证成功后的方法*/@Overrideprotected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain,Authentication auth) {if (auth.getPrincipal() == null) {throw new CustomException(ResponseEnum.PARAM_ERROR.getCode(),"用户名或密码错误!");}String token = UUID.randomUUID().toString().replaceAll("-","");Map<String, String> result = new HashMap<>();result.put("token", token);redisTemplate.opsForValue().set(TOKEN_PREF_DEFAULT + token, JSONObject.toJSONString(auth.getPrincipal()), REDIS_TIME_OUT);ResponseUtil.out(response, Result.data(result));//        CustomUser user = (CustomUser) auth.getPrincipal();//        //2.生成token//        String token = JwtHelper.createToken(null, user.getUsername());//        //3.返回(通过响应工具)//        Map<String, Object> map = new HashMap<>();//        map.put("token", token);//        request.getSession().setAttribute("token",token);//        ResponseUtil.out(response, Result.data(result));}/*** 认证失败后的方法*/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request,HttpServletResponse response,AuthenticationException e) {e.printStackTrace();ResponseUtil.out(response, new Result(ResponseEnum.PARAM_ERROR.getCode(),"用户名或密码错误!", null));}
}

十三、认证过滤器配置

import com.alibaba.fastjson.JSONObject;
import com.wgz.auth.constant.ResponseEnum;
import com.wgz.auth.constant.Result;
import com.wgz.auth.entity.user.UserVo;
import com.wgz.auth.util.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
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 static com.wgz.auth.constant.Common.TOKEN_PREF_DEFAULT;
import static com.wgz.auth.constant.Common.TOKEN_PREF_INTERFACE;/*** token认证过滤器:* 1.对没有开放拦截的接口进行token认证* 2.认证成功放行、认证失败拦截并提示* PS: 注册方式 addFilterBefore or addFilterAfter*/
public class TokenAuthenticationFilter extends OncePerRequestFilter {private RedisTemplate<String, String> redisTemplate;/*** 初始化:资源准备*/public TokenAuthenticationFilter(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;}/*** 拦截没有未开放的接口并认证token是否有效,* token有效放行、token无效拦截并提示*/@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {//1.获取认证信息UsernamePasswordAuthenticationToken authentication = getAuthentication(request, response);//2.将认证信息存储到SecurityContextHolder对象中SecurityContextHolder.getContext().setAuthentication(authentication);filterChain.doFilter(request, response);}/*** 获取用户信息,一级校验token* 一、TOKEN_PREF_DEFAULT:* 1.spring security 过滤器登录方式:在TokenLoginFilter(继承 UsernamePasswordAuthenticationFilter )的attemptAuthentication方法中验证登录信息、* 2.在TokenLoginFilter中指定登录接口* 二、TOKEN_PREF_INTERFACE* 1.spring security 自定义接口登录方式: 在自己的controller接口内验证登录信息、* 2.在SecurityConfig放开指定的登录接口*/private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) {String token = request.getHeader("token");if (!StringUtils.hasLength(token)) {return null;}//        //自定义的接口登录方式//        String redisStr = redisTemplate.opsForValue().get(TOKEN_PREF_INTERFACE + token);//默认的继承过滤器方式String redisStr = redisTemplate.opsForValue().get(TOKEN_PREF_DEFAULT + token);if (!StringUtils.hasLength(redisStr)) {ResponseUtil.out(response, new Result(ResponseEnum.NOT_PERMISSION.getCode(),"无效的token,请重新登录!", null));return null;}UserVo user = JSONObject.parseObject(redisStr, UserVo.class);if (ObjectUtils.isEmpty(user)) {ResponseUtil.out(response, new Result(ResponseEnum.SERVICE_ERROR.getCode(),"认证信息转换失败,请联系客户或管理员!", null));return null;}//1.从redis中获取权限信息//2.将权限信息传给 UsernamePasswordAuthenticationToken//返回认证信息return new UsernamePasswordAuthenticationToken(user.getUserName(),null, null);}
}

十四、自定义UserDetailServiceImpl
 

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wgz.auth.entity.security.CustomUser;
import com.wgz.auth.entity.user.UserVo;
import com.wgz.auth.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.util.List;@Service
@RequiredArgsConstructor
public class UserDetailServiceImpl implements UserDetailsService {private final UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {LambdaQueryWrapper<UserVo> lambdaQueryUser = new LambdaQueryWrapper<>();lambdaQueryUser.eq(UserVo: :getUserName, username);lambdaQueryUser.eq(UserVo: :getIsDelete,0);lambdaQueryUser.last("limit 1");UserVo userVo = userMapper.selectOne(lambdaQueryUser);//判断用户是否为空if (null == userVo) {throw new UsernameNotFoundException("用户不存在");}//判断用户状态是否可用if (userVo.getStatus() == 0) {throw new RuntimeException("用户已禁用...");}//手动设置权限,也可以通过数据库查询获取List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("addUser,findAll,ROLE_admin,ROLE_user");return new CustomUser(userVo, auths);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/192513.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

态势感知是什么

在当今高度信息化的时代&#xff0c;信息安全风险已经成为企业、政府和个人的重要关注点。为了有效应对这些风险&#xff0c;态势感知成为了一种日益重要的能力。态势感知是一种基于环境的、动态、整体地洞悉安全风险的能力&#xff0c;是以安全大数据为基础&#xff0c;从全局…

软件产品经理常用的ChatGPT通用提示词模板

产品规划和发展战略&#xff1a;请帮助我制定软件产品的规划和发展战略&#xff0c;包括市场调研、用户需求分析、产品定位、竞争对手分析等方面的内容&#xff0c;以便我能够更好地把握市场机会并制定相应的发展策略。 产品设计和开发&#xff1a;请帮助我进行软件产品的设计…

听GPT 讲Rust源代码--src/tools(5)

File: rust/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs 在Rust源代码中&#xff0c;lower.rs文件位于Rust Analyzer项目的hir-ty子库中&#xff0c;其目的是将高级中间表示&#xff08;HIR&#xff09;降低为中间表示&#xff08;MIR&#xff09;。下面对文件及其…

Python----文件备份案例

实例代码 # 1、接收用户输入的文件名&#xff08;要备份的文件名&#xff09; oldname input(请输入要备份的文件名称&#xff1a;) # python.txt # 2、规划备份文件名&#xff08;python[备份].txt&#xff09; # 搜索点号 index oldname.rfind(.) # 返回文件名和文件后缀 …

数据结构-归并排序

归并排序 基本概念 归并是指将两个或两个以上的有序表合并成一个有序表。 基本思想 假设有N个记录&#xff0c;则可以看成是N个有序的子序列&#xff0c;每个子序列的长度为1&#xff0c;然后两两归并得到[n/2] 个&#xff08;上取整&#xff09;长度为2的子序列&#xff…

css实现最简单的3d透视效果,通过旋转可以直观感受到

css的3d效果还是非常复杂的&#xff0c;我今天简单学习了一下入门&#xff0c;实现了一个超级简单的效果&#xff0c;帮助我自己理解这个3d的过程&#xff0c;实现的效果动画如下&#xff1a;可以通过调整父元素旋转的角度&#xff0c;更加直观的感受这个3d效果&#xff1a; 实…

【开源视频联动物联网平台】j2mod库对指令码的定义

在J2Mod库中&#xff0c;Modbus通信使用指令码来标识要执行的操作。指令码&#xff08;Function Code&#xff09;是Modbus协议中用于定义请求和响应类型的数字代码。不同的指令码表示不同的功能&#xff0c;例如读取保持寄存器、写入单个寄存器等。以下是一些常见的Modbus指令…

C/S与B/S的区别

B/S与C/S理解 C/S结构B/S结构问题数据放在服务器端与客户端的利弊 C/S结构 客户端&#xff1a;用户安装的软件 服务端&#xff1a;统一管理数据库的主机中的软件 叫做服务端。 B/S结构 用户通过浏览器实现&#xff08;往往表示业务逻辑在前端进行实现&#xff0c;主要业务逻…

【数据结构初阶】双向链表

各位读者老爷好&#xff0c;很高兴你又来读本鼠鼠的博客。鼠鼠我呀基于C语言实现一下双向链表&#xff0c;有兴趣的读者老爷可以瞅瞅哈&#xff01; 目录 1.定义双向链表节点 2.初始化哨兵位 3.双向链表销毁 4.双向链表打印 5.双向链表在pos的前面进行插入 6.双向链表删除…

初学者如何入门深度学习:以手写数字字符识别为例看AI 的学习路径,一图胜千言!超多高清大图收集整理

文章大纲 深度神经网络机器学习,深度学习,数据发掘之间的关系理解深度神经网络最好的可视化工具深度学习基础概念能解决神马种类的问题?卷积池化以手写字符识别为例讲述深度学习的分类问题MNIST 数据集简介初学者入门 :生成式 AI -- generative-ai-for-beginners从神经网络…

Linux系统之部署Plik临时文件上传系统

Linux系统之部署Plik临时文件上传系统 一、Plik介绍1.1 Plik简介1.2 Plik特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本 四、下载Plik软件包4.1 创建下载目录4.2 下载Plik软件包4.3 查看下载的Plik软件…

3.4_1 java自制小工具 - pdf批量转图片

相关链接 目录参考文章&#xff1a;pdf转图片(apache pdfbox)参考文章&#xff1a;GUI界面-awt参考文章&#xff1a;jar包转exe(exe4j)参考文章&#xff1a;IDEA导入GIT项目参考文章&#xff1a;IDEA中使用Gitee管理代码gitee项目链接&#xff1a;pdf_2_image网盘地址&#xf…

Mysql安全之基础合规配置

一、背景 某次某平台进行安全性符合型评估时&#xff0c;列出了数据库相关安全选项&#xff0c;本文特对此记录&#xff0c;以供备忘参考。 二、安全配置 2.1、数据库系统登录时的用户进行身份标识和鉴别&#xff1b; 1&#xff09;对登录Mysql系统用户的密码复杂度是否有要…

Nacos 客户端版本从1.x 升级到 2.x 的排坑记

问题描述 应用引入 Nacos Config 配置管理功能&#xff0c;应用启动时读取 Nacos 配置中心的配置作为启动参数&#xff0c;其中包括数据源信息 url 。 当 Nacos 正在进行 GC 操作、无法响应客户端请求时&#xff0c;应用端刚启动时发送的登录认证请求 http://IP:PORT/nacos/v…

Azure Machine Learning - 使用 Python 进行语义排名

在 Azure AI 搜索中&#xff0c;语义排名是查询端功能&#xff0c;它使用 Microsoft AI 对搜索结果重新评分&#xff0c;将具有更多语义相关性的结果移动到列表顶部。 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&am…

单链表的模拟实现

单链表的模拟实现 一&#xff1a;单链表的概念&#xff1a;二&#xff1a;单链表中的方法&#xff1a;1&#xff1a;得到单链表的长度2&#xff1a;查找是否包含关键字key是否在单链表当中3&#xff1a;打印单链表中的数据&#xff1a;display&#xff08;&#xff09;3&#x…

WPF实现文字纵向排布的TabItem

文章目录 基本用法文字竖排显示 WPF布局 基本用法 WPF中的TabControl是一个容器控件&#xff0c;用于在单个窗体或页面中承载多个选项卡。每个选项卡可以包含不同的控件&#xff0c;用于显示不同的内容&#xff0c;其最简单的调用方法如下&#xff0c;只需在TabControl中无脑…

1.3 Linux文件系统

一、Linux文件系统结构 Linux下都是文件&#xff0c;所以没有Windows一样的盘&#xff0c;有的只有文件夹。 cd /    // 进入根目录 ls     // 查看根目录"/"下的文件及文件夹 /bin    &#xff1a;存储了很多系统命令&#xff0c; /usr/sbin 也存储了…

未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序报错的解决办法

当在本地计算机上使用Microsoft Office相关库时&#xff0c;可能会出现“未在本地计算机上注册microsoft.ACE.oledb.12.0”提供程序的报错。这是由于缺少相关的驱动程序或者未安装相应的软件所导致的。下面是解决该问题的完整攻略。 可能是因为没有安装数据访问组件&#xff0…

[蓝桥杯 2019 省 B] 特别数的和-C语言的解法

小明对数位中含有 2、0、1、9 的数字很感兴趣&#xff08;不包括前导 0&#xff09;&#xff0c;在 1 到 40 中这样的数包括 1、2、9、10 至 32、39 和 40&#xff0c;共 28 个&#xff0c;他们的和是 574。 请问&#xff0c;在 1 到 n 中&#xff0c;所有这样的数的和是多少&…