SpringSecurity + JWT实现登录认证

前置基础请参考:SpringSecurity入门-CSDN博客

配置:

pom.xml

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.5</version></parent><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><!--redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.23</version></dependency><!--fastjson依赖--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.33</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--jwt依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency><!--mysql + mybatis--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.5</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>1.0.9.RELEASE</version></dependency><dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>2.1.2.RELEASE</version></dependency></dependencies>

application.yaml

server:port: 8080
spring:data:redis:host: 你的redisurlport: 6379password: 你的密码datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: 你的数据库urlusername: 你的用户名password: 你的密码#配置security的默认账号和密码
#  security:
#    user:
#      name: admin
#      password: admin
mybatis-plus:mapper-locations: classpath:/mapper/*.xml

一、自定义登录

1、实现UserDetailsService

@Service
public class UserDetailsServiceImpl implements UserDetailsService {private UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>().eq(User::getName, username);User user = userMapper.selectOne(queryWrapper);if(null == user){throw new RuntimeException("用户名或密码错误");}LoginUser loginUser = new LoginUser(user);return loginUser;}
}

我这里使用的是根据用户名查询,一般情况下是使用userid,这个时候可能会报异常,因为此时的密码校验方式是以明文的形式,所以我们需要将加密器注入到

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic PasswordEncoder PasswordEncoder(){return new BCryptPasswordEncoder();}
}

2、自定义统一结果返回对象

@Data
@AllArgsConstructor
public class ResponseResult {private Integer Code; //状态码private String message; //信息private Object data; //数据
}

3、自定义登录接口

@RestController
@RequestMapping("/user")
public class HelloController {@Autowiredprivate LoginService loginService;@PostMapping("/login")public ResponseResult login(@RequestBody User user){ResponseResult result = loginService.login(user);return result;}@RequestMapping("/success")public ResponseResult success(){Authentication authentication = SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();ResponseResult responseResult = new ResponseResult(200, "success", loginUser);return responseResult;}@RequestMapping("/fail")public String fail(){return "fail";}
}

二、使用jwt令牌

1、自定义登录方式及其实现

在这里我们需要提取出用户信息,将用户信息存入redis缓存之中,并且使用用户id生成jwt令牌,将jwt令牌封装进返回体中,调用其他接口时就可以从jwt令牌中提取出用户id,然后去redis中提取用户信息。

public interface LoginService {public ResponseResult login(User user);
}@Service
public class LoginServiceImpl implements LoginService {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate RedisTemplate<String,String> redisTemplate;@Overridepublic ResponseResult login(User user) {//获取认证方法进行用户认证AuthenticationUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getName(), user.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);//认证未通过if(ObjectUtils.isNull()){throw new RuntimeException("用户名或者密码错误");}//提取用户信息LoginUser loginuser = (LoginUser) authenticate.getPrincipal();String id = loginuser.getUser().getId().toString();//生成jwtString jwt = JwtUtils.createJWT(id);//将用户信息存入redisString jsonString = JSON.toJSONString(loginuser);redisTemplate.opsForValue().set("userId:" + id,jsonString,3, TimeUnit.MINUTES);//将jwt存入tokenHashMap<String, String> map = new HashMap<>();map.put("token",jwt);ResponseResult responseResult = new ResponseResult(200, "success", map);//返回return responseResult;}
}

jwtutils:

public class JwtUtils {//有效期为public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时//设置秘钥明文public static final String JWT_KEY = "sangeng";public static String getUUID(){String token = UUID.randomUUID().toString().replaceAll("-", "");return token;}/*** 生成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();}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=JwtUtils.JWT_TTL;}long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);return Jwts.builder().setId(uuid)              //唯一的ID.setSubject(subject)   // 主题  可以是JSON数据.setIssuer("sg")     // 签发者.setIssuedAt(now)      // 签发时间.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥.setExpiration(expDate);}/*** 创建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();}public static void main(String[] args) throws Exception {
//        String jwt = createJWT("2123");
//        Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyOTY2ZGE3NGYyZGM0ZDAxOGU1OWYwNjBkYmZkMjZhMSIsInN1YiI6IjIiLCJpc3MiOiJzZyIsImlhdCI6MTYzOTk2MjU1MCwiZXhwIjoxNjM5OTY2MTUwfQ.NluqZnyJ0gHz-2wBIari2r3XpPp06UMn4JS2sWHILs0");
//        String subject = claims.getSubject();
//        System.out.println(subject);
//        System.out.println(claims);//String jwt = createJWT("1234");Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwNjc3ZTE4NDJhMTg0MDNjYmE5MDM3ZjUwYzUwYWFlMyIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTcxNDkxMzE0NiwiZXhwIjoxNzE0OTE2NzQ2fQ.7IsMNeWewvNqoZB5Wys0cagCqv4m014DZNrNGZRjB_E");String subject = claims.getSubject();System.out.println("subject = " + subject);}/*** 生成加密后的秘钥 secretKey* @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.getDecoder().decode(JwtUtils.JWT_KEY);SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}/*** 解析** @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}}

2、webmvc和security配置

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
}@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic PasswordEncoder PasswordEncoder(){return new BCryptPasswordEncoder();}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// 禁用basic明文验证//.httpBasic().disable()// 前后端分离架构不需要csrf保护.csrf().disable()// 禁用默认登录页//.formLogin().disable()// 禁用默认登出页//.logout().disable()// 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint//.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(invalidAuthenticationEntryPoint))// 前后端分离是无状态的,不需要session了,直接禁用。.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests// 允许所有OPTIONS请求//.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 允许直接访问授权登录接口.requestMatchers(HttpMethod.POST, "/user/login").permitAll()// 允许 SpringMVC 的默认错误地址匿名访问//.requestMatchers("/error").permitAll()// 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")// 允许任意请求被已登录用户访问,不检查Authority.anyRequest().authenticated());//.authenticationProvider(authenticationProvider())// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter//.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}
}

3、用户信息存入SecurityContextHolder

为了简化操作流程,方便我们在编写其他接口时可以很方便的提取出用户信息,我们可以自定义过滤器,提取出用户信息,并且存入SecurityContextHodler里

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisTemplate<String,String> redisTemplate;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//从请求头中提取出用户idString jwt = request.getHeader("token");//id不存在if(!StringUtils.hasText(jwt)){filterChain.doFilter(request,response);return;}//解析jwtString id = null;try {Claims claims = JwtUtils.parseJWT(jwt);id = claims.getSubject();} catch (Exception e) {e.printStackTrace();filterChain.doFilter(request,response);}//使用用户id从redis里面提取出用户信息String jsonstring = redisTemplate.opsForValue().get("userId:" + id);LoginUser loginUser = JSON.parseObject(jsonstring, LoginUser.class);//将用户信息存入securityContextHolderUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request,response);}
}

需要添加我们自定义的过滤器,使其生效,添加.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

    @Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// 禁用basic明文验证//.httpBasic().disable()// 前后端分离架构不需要csrf保护.csrf().disable()// 禁用默认登录页//.formLogin().disable()// 禁用默认登出页//.logout().disable()// 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint//.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(invalidAuthenticationEntryPoint))// 前后端分离是无状态的,不需要session了,直接禁用。.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests// 允许所有OPTIONS请求//.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 允许直接访问授权登录接口.requestMatchers(HttpMethod.POST, "/user/login").permitAll()// 允许 SpringMVC 的默认错误地址匿名访问//.requestMatchers("/error").permitAll()// 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")// 允许任意请求被已登录用户访问,不检查Authority.anyRequest().authenticated())//.authenticationProvider(authenticationProvider())// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}
}

三、测试

因为登录需要使用post请求,所以我们使用postman进行测试

需求:

       1、登录

localhost:8080/user/login 登陆成功返回信息里携带jwt令牌

        2、测试jwt令牌

localhost:8080/user/success携带jwt令牌提取用户信息

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

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

相关文章

Xilinx 千兆以太网TEMAC IP核简介

Xilinx 公司提供了千兆以太网MAC控制器的可参数化LogiCORET™IP解决方案&#xff0c;通过这个IPCore可以实现FPGA与外部网络物理层芯片的互连。基于Xilinx FPGA 的以太网设计&#xff0c;大大降低了工程的设计复杂度&#xff0c;缩短了开发周期&#xff0c;加快了产品的面市速度…

【数据结构】第五讲:栈和队列

个人主页&#xff1a;深情秋刀鱼-CSDN博客 数据结构专栏&#xff1a;数据结构与算法 源码获取&#xff1a;数据结构: 上传我写的关于数据结构的代码 (gitee.com) 目录 一、栈 1.栈的定义 2.栈的实现 a.栈结构的定义 b.初始化 c.扩容 d.入栈 e.出栈 f.打印 g.取栈顶元素…

对camera raw中的纹理和清晰度的内容的修正(之前的内容写错了,懒得改了重新写一篇)

之前对于环的解释&#xff0c;不太行&#xff0c;这里我给出进一步地说明。 首先对环的解释: 我这里说的环指的是频域段中的ai变化的时候对图像像素的变化的极大的影响程度的环状效果&#xff0c;会出现不规则的环状的提亮或增暗的效果。实际上是每个fj都有影响&#xff0c;但…

《数据结构与算法之美》学习笔记一

前言&#xff1a;今天开始学习极客时间的课程《数据结构与算法之美》。为撒要学习这个&#xff1f;因为做力扣题太费劲了&#xff0c;自己的基础太差了&#xff01;所以要学习学习。开一个系列记录一下学习笔记。认真学吧&#xff0c;学有所获才不负韶华&#xff01;之前就学过…

[蓝桥杯]真题讲解:AB路线(BFS+分层图)

[蓝桥杯]真题讲解&#xff1a;AB路线&#xff08;BFS分层图&#xff09; 一、视频讲解二、正解代码1、C2、python33、Java 一、视频讲解 [蓝桥杯]真题讲解&#xff1a;AB路线&#xff08;BFS分层图&#xff09; 二、正解代码 1、C #include<bits/stdc.h> #define INF …

现场工程师出手--虚拟化软件预留内存过大导致其他程序崩溃问题

项目场景&#xff1a; 一位学生有一台笔记本电脑&#xff0c;安装了Android&#xff0c;Kafka虚拟机很多软件。笔记本配置了20GB内存&#xff0c;固态硬盘&#xff0c;但最近很卡&#xff0c;Android Stuido经常闪退&#xff0c;一些游戏也无法运行。 问题描述 由于Android S…

【python量化交易】qteasy使用教程05——创建第一个自定义交易策略

创建第一个自定义交易策略 使用qteasy创建自定义交易策略开始前的准备工作本节的目标自定义策略的实现方法使用 qteasy 的 Strategy 策略类三种不同的自定义策略基类定义一个双均线择时交易策略定义策略运行时机定义策略需要的数据自定义交易策略的实现&#xff1a;realize()获…

电子学会C/C++编程等级考试2024年03月(四级)真题解析

C/C++编程(1~8级)全部真题・点这里 第1题:最长上升子序列 一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK…

自适应调节Q和R的自适应UKF(AUKF_QR)的MATLAB程序

简述 基于三维模型的UKF&#xff0c;设计一段时间的输入状态误差较大&#xff0c;此时通过对比预测的状态值与观测值的残差&#xff0c;在相应的情况下自适应调节系统协方差Q和观测协方差R&#xff0c;构成自适应无迹卡尔曼滤波&#xff08;AUKF&#xff09;&#xff0c;与传统…

【敦煌网注册/登录安全分析报告】

敦煌网注册/登录安全分析报告 前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大…

广交会资料缺了时效性有用吗?白忙活了

广交会资料开发客户有哪些难度&#xff1f; 1.如何辨别数据的真实性 展会客户真实可信&#xff0c;无需求者不会远道而来。但部分采购商为保护隐私和简便行事&#xff0c;联系方式有所保留。因此&#xff0c;数据筛选与深挖需耗费精力和耐心。 2.历史陈旧数据具备客户开发价值…

[Bug]:由于中国防火墙,无法连接 huggingface.co

问题描述 : OSError: We couldnt connect to https://huggingface.co to load this file, couldnt find it in the cached files and it looks like youscan/ukr-roberta-base is not the path to a directory containing a file named config. Json. Checkout your internet …