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: 你的redisurl
      port: 6379
      password: 你的密码
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: 你的数据库url
    username: 你的用户名
    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;

    @Override
    public 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 {
    @Bean
    public 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 {
    @Autowired
    private 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 {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public ResponseResult login(User user) {
        //获取认证方法进行用户认证Authentication
        UsernamePasswordAuthenticationToken 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();
        //生成jwt
        String jwt = JwtUtils.createJWT(id);
        //将用户信息存入redis
        String jsonString = JSON.toJSONString(loginuser);
        redisTemplate.opsForValue().set("userId:" + id,jsonString,3, TimeUnit.MINUTES);
        //将jwt存入token
        HashMap<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 {
    @Bean
    public PasswordEncoder PasswordEncoder(){
        return new BCryptPasswordEncoder();
    }


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }


    @Bean
    public 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 {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //从请求头中提取出用户id
        String jwt = request.getHeader("token");
        //id不存在
        if(!StringUtils.hasText(jwt)){
            filterChain.doFilter(request,response);
            return;
        }
        //解析jwt
        String 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);
        //将用户信息存入securityContextHolder
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request,response);
    }
}

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

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Bean
    public 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令牌提取用户信息

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

二手交易系统基于Uniapp+FastAdmin+ThinkPHP(源码搭建/上线/运营/售后/更新)

​一款基于UniappFastAdminThinkPHP开发的二手交易系统&#xff0c;卖家可以发布二手信息&#xff0c;买家可以在线询价&#xff0c;支持在线聊天(高级授权)&#xff0c;在线购买支付&#xff0c;支持发布高价回收(高级授权)信息。自带社交板块&#xff0c;用户可以发布帖子、加…

【论文笔记】DiL-NeRF: Delving into Lidar for Neural Radiance Field on Street Scenes

原文链接&#xff1a;https://arxiv.org/abs/2405.00900 1. 引言 自动驾驶等应用领域需要逼真的仿真。传统的仿真流程需要手工创建3D资产并构成虚拟环境&#xff0c;但其人力和专业需求使其难以具有可放缩性。 NeRF有不错的仿真能力&#xff0c;但需要大范围覆盖的训练数据以…

【MySQL的内置函数】

文章目录 一、日期函数1.current_date()2.current_time()3.current_timestamp4. date_add 穿越未来5.date_sub 回到过去6.datediff案例 二、字符串函数2.1charset2.2 concat ——拼接字符串2.3 ucase——转化成大写2.4 lcase——转化成小写2.5 left&#xff08;&#xff09;2.6…

计算机毕业设计 | springboot+vue小米商城 购物网站管理系统(源码+论文+讲解视频)

1&#xff0c;项目背景 国家大力推进信息化建设的大背景下&#xff0c;城市网络基础设施和信息化应用水平得到了极大的提高和提高。特别是在经济发达的沿海地区&#xff0c;商业和服务业也比较发达&#xff0c;公众接受新事物的能力和消费水平也比较高。开展商贸流通产业的信息…

无管理员权限linux系统手动切换cuda版本

查看当前计算机cudatoolkit的版本 nvcc -V下载 如果想切换到指定版本&#xff0c;则去官网下载&#xff08;10.1版本为例&#xff09;&#xff1a; cuda下载 cudnn下载 将下载好的文件放到服务器的文件夹中 安装 安装cuda sh cuda_10.1.243_418.87.00_linux.run选择接受 …

教你一招,一键解锁云盘与NAS自动同步!

想象一下 数字生活就像一座繁忙的都市 云盘是你的空中阁楼 俯瞰着整个城市的美景 而NAS是坚实的地基 承载着所有珍贵的记忆 那如何让数据在 城市的高空与地面之间自由穿梭呢&#xff1f; 别急&#xff01;铁威马来帮你&#xff01; TOS 6创新功能&#xff1a;云盘挂载 …

STM32CubeMx 生成IAR工程Jlink无法Debug

肯定是工程设置问题喽&#xff1a; 设置OK!

java小知识:图片添加中文水印,部署到linux环境乱码解决

源码如下&#xff1a; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Objects;/*** 图片添加文字水印** param so…

Rust学习笔记(上)

前言 笔记的内容主要参考与《Rust 程序设计语言》&#xff0c;一些也参考了《通过例子学 Rust》和《Rust语言圣经》。 Rust学习笔记分为上中下&#xff0c;其它两个地址在Rust学习笔记&#xff08;中&#xff09;和Rust学习笔记&#xff08;下&#xff09;。 编译与运行 Ru…

璩静也是受害者

5月7日&#xff0c;“百度副总裁璩静称员工闹分手提离职秒批”话题登上了热搜。在短视频里&#xff0c;璩静是会连续出差50天的“公关人”&#xff0c;没有春节周末、没有假期&#xff0c;她会说“员工闹分手提离职我秒批&#xff0c;为什么要考虑员工的家庭”。有网友对其视频…

知识库优劣详解:牵牛易帮 VS HelpLook AI知识库

知识库不仅可以帮助企业有效管理知识&#xff0c;还能提高员工工作效率和质量&#xff0c;因此越来越多的企业选择搭建知识库。在众多搭建知识库的工具中&#xff0c;有的企业会选择免费的牵牛易帮&#xff0c;有的则会更加倾向于付费的HelpLook AI知识库。其中的原因有很多。今…

力扣数据库题库学习(5.11日)--176. 第二高的薪水

176. 第二高的薪水 问题链接 分析 题目要求&#xff1a; 查询并返回 Employee 表中第二高的薪水 。如果不存在第二高的薪水&#xff0c;查询应该返回 null 。 下面是示例&#xff1a; 查询结果如下例所示。 示例 1&#xff1a;输入&#xff1a; Employee 表&#xff1a; --…

Java——类与对象

目录 一、面向对象的初步认识 1.1 什么是面向对象 1.2 面向对象与面向过程 二、类的定义与使用 2.1 简单认识类 2.2 类的定义格式 三、类的实例化 3.1 什么是实例化 3.2 类和对象的说明 四、this引用 4.1 为什么要有this引用 4.2 什么是this引用 ​编辑 4.3 this引用…

verilog中输入序列连续的序列检测

编写一个序列检测模块&#xff0c;检测输入信号a是否满足01110001序列&#xff0c;当信号满足该序列&#xff0c;给出指示信号match。 模块的接口信号图如下&#xff1a; 代码如下&#xff1a; &#xff08;CSDN代码块不支持Verilog&#xff0c;代码复制到notepad编辑器中&am…

pytest + yaml 框架 - 录制接口转 yaml 用例实现

pytest yaml 框架基本不用写 python 代码&#xff0c;只需写yaml 文件用例就能实现接口自动化。 现在引入接口录制功能&#xff0c;连 yaml 文件也不用写了&#xff0c;点点点就能生成 yaml 用例文件了。 录制功能在v1.3.4版本上实现 pip instal pytest-yaml-yoyo 环境准备 …

基于RFID的博物馆藏品管理系统

传统的博物馆藏品管理主要依赖人工记录和物理检查&#xff0c;这样的管理方式耗时耗力&#xff0c;并且容易出现记录错误。另外&#xff0c;对于藏品的定位和监控也存在一定的局限性。 RFID是一种无线通信技术&#xff0c;通过无线电频率识别标签上存储的信息&#xff0c;实现物…

代码随想录第五十天|最佳买卖股票时机含冷冻期、买卖股票的最佳时机含手续费

题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 最佳买卖股票时机含冷冻期与打家劫舍的题目有异曲同工之妙&#xff0c;主要是出现了天数的间隔&#xff0c;一次需要在买卖股票的最佳时机II 题目上做一点调整&#xff0c;代码如下&#xff1a; 如代码所示&…

24数维杯ABC题思路已更新!!!!

24数维杯A题保姆级思路&#xff0b;配套代码&#xff0b;后续参考论文 简单麦麦https://www.jdmm.cc/file/2710639/ 24数维杯B题保姆级思路&#xff0b;可执行代码&#xff0b;后续参考论文 简单麦麦https://www.jdmm.cc/file/2710640/ 24数维杯C题保姆级思路&#xff0b;可执…

在wine里启动Arctium Game Launcher.exe

尝试在wine里启动Arctium Game Launcher.exe 第一次直接启动wine Arctium Game Launcher.exe&#xff0c;结果报错&#xff0c;因为字符的因素&#xff0c;看不出是为啥报错。不过一般是c库有些问题。 为了安装c库&#xff0c;尝试了下载Visual c 安装&#xff0c;失败。尝试…

Windows 11 Manager (Win11系统优化大师) 中文破姐版 v1.4.3

01 软件介绍 ​Windows 11 Manager v1.4.3是一款综合性的系统优化工具&#xff0c;专为Win11设计。该工具包含超过40种功能&#xff0c;旨在全方位提升操作系统的性能。通过这些工具&#xff0c;用户可以对Windows 11进行深度优化和微调&#xff0c;清除不必要的文件&#xff…