SpringSecurity + JWT实现登录认证

news2024/11/17 5:47:03

前置基础请参考: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令牌提取用户信息

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

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

相关文章

网络1--通信过程的理解

1.封装与解包 通信的过程就是不断的封装和解包的过程 封装即就是按照“应用”“传输” “网络” “链路” 层&#xff0c;封装给每一层都加上相应的包头&#xff08;每一层都有协议&#xff0c;&#xff09;解包就是接受到的包文被一层层去掉相对应的包头。 任何一层的协议都…

【Qt】界面定制艺术:光标(cursor)、字体(font)、提示(toolTip)、焦点(focusPolicy)与样式表(styleSheet)的深度探索

文章目录 前言&#xff1a;1. cursor: 设置按钮的光标2. front&#xff1a;设置字体3. toolTip: 鼠标悬停提示4. focusPolicy&#xff1a;设置控件获取到焦点的策略5. styleSheet : 样式表总结&#xff1a; 前言&#xff1a; 在现代软件开发中&#xff0c;用户界面(UI)的设计和…

记录一些常用的网站

开发者搜索-Beta-让技术搜索更简单高效 (baidu.com)https://kaifa.baidu.com/JSON在线 | JSON解析格式化—SO JSON在线工具https://www.sojson.com/DCloud 插件市场https://ext.dcloud.net.cn/uni-app官网 (dcloud.net.cn)https://uniapp.dcloud.net.cn/在线学习书籍https://gi…

C++学习第三十课:C++异常处理机制应用

一、引言 在编程过程中&#xff0c;我们经常会遇到一些无法预见或难以预料的特殊情况&#xff0c;这些情况统称为“异常”。异常可能是由用户输入错误、资源不足、硬件故障或程序逻辑错误等原因引起的。当这些异常情况发生时&#xff0c;如果程序没有适当的处理机制&#xff0…

MySQL数据库表的创建DDL语句(21-25)

21.用户反馈表 CREATE TABLE 7_feedback (feedbackId int(11) NOT NULL AUTO_INCREMENT COMMENT ID,feedbackType int(4) NOT NULL DEFAULT 0 COMMENT 反馈类型&#xff0c;内容来自源系统基础数据表,userId int(11) DEFAULT NULL COMMENT 反馈者ID,creatTime datetime NOT NU…

商场综合体能源监管平台,实现能源高效管理

商场作为大型综合体建筑&#xff0c;其能源消耗一直是备受关注的问题。为了有效管理商场能耗&#xff0c;提高商场能源效率&#xff0c;商场综合体能源监管平台应运而生。 商场综合体能源监管平台可通过软硬件一起进行节能监管&#xff0c;硬件设备包括各种传感器、监测仪表和…

ArcGIS10.2系列许可到期解决方案

本文手机码字&#xff0c;不排版了。 昨晚&#xff08;2021\12\17&#xff09;12点后&#xff0c;收到很多学员反馈 ArcGIS10.2系列软件突然崩溃。更有的&#xff0c;今天全单位崩溃。 ​ 提示许可15天内到期。之前大部分许可是到2021年1月1日的。 ​ 后续的版本许可都是永久的…

哪些企业需要用OV 证书?怎么获取OV证书?看这里

OV证书相当于DV证书而言&#xff0c;其安全等级高&#xff0c;兼容性强&#xff0c;稳定性好&#xff0c;那么哪些企业适用OV证书呢&#xff1f; 1 政府与公共服务网站 政府机构及提供公共服务的网站&#xff0c;必须确保数据的隐私和安全&#xff0c;有助于增强公众对在线服务…

台服dnf局域网搭建,学习用笔记

台服dnf局域网搭建 前置条件虚拟机初始化上传安装脚本以及其他文件至虚拟机密钥publickey.pem客户端配置如果IP地址填写有误&#xff0c;批量修改IP地址 前置条件 安装有vmvarecentos7.6镜像&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/centos-vault/7.6.1810/isos/x86…

拥有蝴蝶效应的爬虫如何进行防护

美国气象学家爱德华罗伦兹&#xff08;Edward N.Lorenz&#xff09;1963年在一篇提交纽约科学院的论文中分析了一个叫做蝴蝶效应的理论&#xff1a;“一个气象学家提及&#xff0c;如果这个理论被证明正确&#xff0c;一只海鸥扇动翅膀足以永远改变天气变化。”在以后的演讲和论…

输出正射图时,分辨率怎么填写整幅输出?

答&#xff1a;设置完输出路径、分辨率、坐标系后&#xff0c;会给图像宽高&#xff0c;根据最大值设置分幅尺寸就可以。 DasViewer是由大势智慧自主研发的免费的实景三维模型浏览器,采用多细节层次模型逐步自适应加载技术,让用户在极低的电脑配置下,也能流畅的加载较大规模实景…

红龙工业设备制造有限公司亮相2024杭州数字物流技术设备展

参展企业介绍 温州红龙工业设备制造有限公司成立于2015年11月。是中国先进的工业皮带设备研发制造和工业皮带整体解决方案运营服务商&#xff0c;现主营皮带接头机、皮带热压机、皮带接驳机、皮带打齿机、输送带打齿机、输送带分层级、输送带导条机、输送带裁切机、高频机等工业…

前端开发指导

前端开发指导 本文介绍了配置前端开发环境需要的软件、配置项等,指导如何开始进行UDM部门前端开发的全流程。本文以Windows系统下在Microsoft Virtual Studio Code中开发为基础。 一、综述 目标:零基础或者新员工依照此文档,能够完成开发环境的搭建及熟悉测试环境的搭建。…

陪诊陪护小程序基于ThinkPHP + FastAdmin + 微信小程序开发(源码搭建/上线/运营/售后/更新

支持多运营区&#xff0c;陪护师、推广者等完整闭环功能&#xff0c;快速搭建陪护业务平台。 消息通知&#xff1a;系统可以向用户发送订单状态变更、陪诊员信息更新等通知&#xff0c;确保用户及时了解相关信息&#xff0c;提高用户体验。 订单管理&#xff1a;患者可以查看自…

QT 小项目:登录注册账号和忘记密码(下一章实现远程登录)

一、环境搭建 参考上一章环境 二、项目工程目录 三、主要源程序如下&#xff1a; registeraccountwindow.cpp 窗口初始化&#xff1a; void registeraccountWindow::reginit() {//去掉&#xff1f;号this->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButt…

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

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

【bug记录】Vue3 Vant UI 中 van-popup 不弹出

原因&#xff1a;语法使用错误&#xff0c;使用了 Vue 2 的语法 Vue3语法&#xff1a; Vue2语法&#xff1a;

JAVA IO/NIO 知识点总结

一、常见 IO 模型简介 1. 阻塞IO模型 最传统的一种IO模型&#xff0c;即在读写数据过程中会发生阻塞现象。当用户线程发出IO请求之后&#xff0c;内核会去查看数据是否就绪&#xff0c;如果没有就绪就会等待数据就绪&#xff0c;而用户线程就会处于阻塞状态&#xff0c;用户线…

企业微信创建应用(一)

登录到企业微信后台管理(https://work.weixin.qq.com/)进入自建应用(应用管理-应用-创建应用) 3.查看参数AgentId和 Secret 4.企业微信查看效果

致远M3 Session 敏感信息泄露漏洞复现

0x01 产品简介 M3移动办公是致远互联打造的一站式智能工作平台,提供全方位的企业移动业务管理,致力于构建以人为中心的智能化移动应用场景,促进人员工作积极性和创造力,提升企业效率和效能,是为企业量身定制的移动智慧协同平台。 0x02 漏洞概述 致远M3 server多个日志文…