spring security 记住我在web和前后端分离如何使用

news2024/9/24 7:16:09

一、传统web开发准备工作

如果不懂原理的话,去看上一篇文章:CSDNicon-default.png?t=N7T8https://mp.csdn.net/mp_blog/creation/editor/141716695

导入需要的依赖包,在传统web页面开发比较简单,我们设置只需要在页面请求参数加上一个remember-me 即可,值可以为:true,on,yes,1 均可

 <dependencies>
        <!-- web 支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- SpringSecurity依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <!-- 简化get set 方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>

        <!-- mybatis 支持 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <!-- mysql 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>

    </dependencies>



    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

1.1  效果图

1.2 前端代码,自定义登录页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <title>登录</title>
    <style type="text/css">
        .form-horizontal{margin-top: 3%;}
    </style>
</head>
<body>
<form role="form" class="form-horizontal" th:action="@{/doLogin.do}" method="post">
    <div class="form-group">
        <label class="control-label col-sm-2">账号</label>
        <div class="col-sm-8">
            <input type="text" class="form-control" name="loginId" placeholder="请输入登录账号">
        </div>
    </div>
    <div class="form-group">
        <label class="control-label col-sm-2">密码</label>
        <div class="col-sm-8">
            <input type="text" class="form-control" name="pwd"  placeholder="请输入密码">
        </div>
    </div>
   <!-- <div class="form-group">
        <label class="control-label col-sm-2">图形验证码</label>
        <div class="col-sm-4">
            <input type="text" class="form-control" name="verifyImg"  placeholder="请输入密码">
        </div>
        <div class="col-sm-4">
            <img src="/comm/kaptcha" id="kaptcha_id">
        </div>
    </div>-->
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <div class="checkbox">
                <label>
                    <input type="checkbox" value="1" name="remember-me">请记住我
                </label>
            </div>
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-default">登录</button>
        </div>
    </div>
</form>

</body>
</html>

1.3 后端代码

该地方cookie是基于数据库的,但是可以使用基于内存的方式;

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


    @Autowired
    private DataSource mysqlDataSource;


//    @Bean
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
//        UserDetails userDetails = User.withUsername("root").password("{noop}123456").authorities("admin").build();
//        userDetailsManager.createUser(userDetails);
//        return userDetailsManager;
//    }


    @Bean
    public UserDetailsService userDetailsService() {
        return new LoginUserServiceImpl();
    }



    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/").permitAll()// 登录页面可直接访问
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/doLogin.do") //登录请求url
                .loginPage("/")//默认登录页面
                .failureForwardUrl("/")//登录失败跳转url
                .defaultSuccessUrl("/hello",true)//登录成功跳转页,总是到 hello页面
                .usernameParameter("loginId")//用户名
                .passwordParameter("pwd")
                .and()
                .rememberMe()//开启记住我的功能
                .rememberMeParameter("remember-me")// 记住我请求参数
                //.rememberMeServices(rememberMeServices())  // 指定rememberMeServices 实现
                .tokenRepository(persistentTokenRepository()) //持久化令牌
                .and()
                .csrf().disable();
    }


    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
        repository.setDataSource(mysqlDataSource);
        //repository.setCreateTableOnStartup(true);//自动创建表结构
        return repository;
}

1.4  设置session 过期时间为1分钟,看登录后cookie值变化

二、自定义页面记住我(前后端分离)

      前后端分离需要自己重写UsernamePasswordAuthenticationFilter 处理json类型的数据,还需要重写AbstractRememberMeServices 中的org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices#rememberMeRequested 中的方法,判断是否是需要记住我,同事需要记传一个UserDetailsService,PersistentTokenRepository 对象;传UserDetailsService的对象是因为记住我解析完cookie之后,会调用 loadUserByUsername的方法重新获取一次用户信息;

2.1 效果图

2.2 代码实现

只有后端代码配置,没有前端页面设置,数据为json格式传输

2.2.1 自定义LoginFilter

用来替代UsernamePasswordAuthenticationFilter 过滤器

public class LoginFilter extends UsernamePasswordAuthenticationFilter {


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        // 判断请求类型是否为json
        if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType()) || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(request.getContentType())) {
            try {
                Map<String,String> userInfMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                String username = userInfMap.get(getUsernameParameter());
                String password = userInfMap.get(getPasswordParameter());

                // 记住我参数
                String remembersValue = userInfMap.get(AbstractRememberMeServices.DEFAULT_PARAMETER);
                request.setAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER,remembersValue);

                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 走默认逻辑
        return super.attemptAuthentication(request, response);
    }
}

2.2.2 自定义RememberMeServices

主要是用来重写rememberMeRequested 拿到记住我的请求参数,

public class CustomerRemembersService extends PersistentTokenBasedRememberMeServices {


    public CustomerRemembersService(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {
        super(key, userDetailsService, tokenRepository);
    }


    @Override
    protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
        String paramValue = request.getAttribute(parameter).toString();
        if (paramValue != null) {
            if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
                    || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
                return true;
            }
        }
        return false;
    }
}

2.2.3 将loginFilter和自定义PersistentTokenBasedRememberMeServices 加入搭配配置中

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


    @Autowired
    private DataSource mysqlDataSource;


//    @Bean
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
//        UserDetails userDetails = User.withUsername("root").password("{noop}123456").authorities("admin").build();
//        userDetailsManager.createUser(userDetails);
//        return userDetailsManager;
//    }


    @Bean
    public UserDetailsService userDetailsService() {
        return new LoginUserServiceImpl();
    }



    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/doLogin.do") //登录请求url
                .and()
                .rememberMe()//开启记住我的功能
                .rememberMeParameter("remember-me")// 记住我请求参数
                .rememberMeServices(rememberMeServices())  // 指定rememberMeServices 实现
                .and()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> {
                    // 未认证提示错误信息
                    Map<String,Object> resMap = new HashMap<>();
                    resMap.put("code","401");
                    resMap.put("msg","请认证后再来请求接口!");

                    WebUtils.writeJson(response,resMap);
                })
                .and()
                .csrf().disable();


        // 替换默认的认证器
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }


    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
        repository.setDataSource(mysqlDataSource);
        return repository;
    }


    @Bean
    public RememberMeServices rememberMeServices() {
        return new CustomerRemembersService("uuid"// 定义一个生成令牌的key
                ,userDetailsService()    //认证数据源
                ,persistentTokenRepository()  // 数据库方式
        );
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setFilterProcessesUrl("/doLogin.do");// 登录请求的url
        loginFilter.setUsernameParameter("loginId");
        loginFilter.setPasswordParameter("pwd");

        // 认证成功时候处理
        loginFilter.setAuthenticationSuccessHandler(((request, response, authentication) -> {
            // 认证失败时候处理
            Map<String,Object> resMap = new HashMap<>();
            resMap.put("code","0000");
            resMap.put("msg","登录成功");
            resMap.put("data",authentication);

            WebUtils.writeJson(response,resMap);
        }));

        // 认证失败时候处理
        loginFilter.setAuthenticationFailureHandler(((request, response, exception) -> {
            Map<String,Object> resMap = new HashMap<>();
            resMap.put("code","401");
            resMap.put("msg",exception.getMessage());

            WebUtils.writeJson(response,resMap);
        }));


        // 设置自定义的认证数据源
        loginFilter.setAuthenticationManager(authenticationManagerBean());

        // 设置记住我,这个地方的记住非第一次登录时候
        loginFilter.setRememberMeServices(rememberMeServices());

        return loginFilter;
    }
}

2.3 最终使用postman 测试接口

2.4 remember 请求参数源码

  为什么请求参数是yes 或者on 都可以,请求的参数的key为remember-me

三、源码资源地址:

https://download.csdn.net/download/qq_36260963/89695919

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

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

相关文章

Linux-gcc/g++使用

文章目录 概念gccg 编译过程预处理(进行宏替换)编译&#xff08;生成汇编&#xff09;汇编&#xff08;生成机器可识别代码&#xff09;连接&#xff08;生成可执行文件或库文件&#xff09;函数库 gcc选项 概念 Linux中的gcc和g是GNU Compiler Collection&#xff08;GNU编译…

RESP图形化界面远程连接虚拟机Redis教程

参考优质大佬文章&#xff1a; Redis安装以及RESP连接Redis服务器_resp 连接-CSDN博客 《Redis&#xff1a;小白入门》RESP远程连接问题_redis配置文件更改为可以远程连接-CSDN博客 目录 环境 第一步&#xff1a;修改redis配置文件 第二步&#xff1a;关闭Linux防火墙 第三…

功能需求文档-自适应巡航控制ACC

本文以特斯拉Model3为例&#xff0c;展示如何撰写其主动巡航控制功能的功能需求文档&#xff1b;详情请参照用户手册 功能概述 主动巡航控制(ACC)是指系统实时监控车辆前方行驶环境&#xff0c;在设定的速度范围内&#xff0c;通过控制油门和制动&#xff0c;自动调整行驶速度…

Mybatis 潦草笔记

准备工作&#xff08;创建springboot工程、数据库表、实体类&#xff09;引入Mybatis的相关依赖&#xff0c;配置Mybatis&#xff08;数据库连接信息&#xff09;编写SQL语句&#xff08;注解/XML&#xff09; 创建springboot工程 选中两项 MyBatis Framework&#xff1a;My…

分支和循环(上)

目录 1. if语句 1.1 if ​1.2 else 1.3 分支中包含多条语句 1.4 嵌套if 1.5 悬空else问题 2. 关系操作符 3. 条件操作符 4. 逻辑操作符 4.1 逻辑取反操作符 4.2 逻辑与运算符 4.3 逻辑或运算符 4.4 连续:闰年的判断 4.5 短路 5. switch语句 5.1 if语句和switch…

28. 双耳配对 - 配置

1. 概述 通过MAC地址的最后一位的奇偶来判断左右耳 2. 验证 右耳:奇数(主耳)-》BT ADDR: 12:42:22:34:34:6d 左耳:偶数(从耳)-》BT ADDR: 12:42:22:34:34:6c

K8S - 理解volumeMounts 中的subpath

在上一篇文章中 springboot service如何动态读取外部配置文件 介绍了springboot 中如何实时读取外部配置文件的内容 部署在K8S 接下来我把它部署在k8s 首先&#xff0c; 我们把配置文件放入项目某个目录 这步部是必须的&#xff0c; 毕竟我们要引入是项目外部的文件&#xf…

TI DSP TMS320F280025 Note9:GPIO输入输出与外部中断功能原理与应用

TMS320F280025 GPIO输入输出与外部中断功能原理与应用 文章目录 TMS320F280025 GPIO输入输出与外部中断功能原理与应用GPIO原理输入输出模式的共同特性1. 复用设置2. 内部上拉设置3. GPIO状态读取 对于输出模式输出电平设置开漏输出设置 对于输入模式极性设置采样类型不同步(异…

CSS3换装达人原理

引言 换装或者是换皮肤是常见的行为&#xff0c;我们可以先看下效果&#xff1a; 选择不同的颜色&#xff0c;就可以秒变人物服装的颜色&#xff0c;原理其实非常简单 实现步骤 主要分为三步&#xff0c;即素材的提供、布局样式、动态控制 图片提供 提供两张图片&#xff…

每日OJ_牛客_红与黑(简单dfs)

目录 牛客_红与黑&#xff08;简单dfs&#xff09; 解析代码 牛客_红与黑&#xff08;简单dfs&#xff09; 红与黑__牛客网 解析代码 循环接收每组用例&#xff0c;对于每组用例进行如下操作&#xff1a; 1. 找到‘’所在的位置&#xff0c;即起始搜索的点 2. 使用DFS搜索地…

20240831-PostgreSQL小课持续更新

PostgreSQL 小课专栏介绍 PostgreSQL 小课目前已累积了近 21 万字。小课最新的大纲&#xff1a; 目前已完成大概 95% 的进度&#xff1a; (venv312) ➜ mypostgres git:(dev) sh scripts/word_statistics_pg_style.shFilename …

【微服务】限流、熔断和降级(持续更新中~)

1、限流 1.1 什么是限流 限流&#xff08;Rate Limiting&#xff09;是一种常用的技术手段&#xff0c;用于控制系统对资源的访问速率&#xff0c;确保系统的稳定性和可靠性。在分布式系统、Web服务、API接口等场景中&#xff0c;限流尤为重要。通过限制请求的频率或数量&…

uniapp u--input实现select下拉列表 input点击事件

背景&#xff1a; 技术框架&#xff1a; uniapp框架(vue2语法)uView组件库。 通过form表单实现数据列表的“查询”功能。注意&#xff1a; 1、<u--form>内部嵌套<u-form-item>&#xff0c;<u-form-item>内部嵌套<u--input>表单组件。 2、H5浏览器端&am…

华为 HCIP-Datacom H12-821 题库

有需要题库的可以看主页置顶 1.MSTP 有不同的端口角色&#xff0c;对此说法不正确的是&#xff1a; A、MSTP 中除边缘端口外&#xff0c;其他端口角色都参与 MSTP 的计算过程 B、MSTP 同一端口在不同的生成树实例中可以担任不同的角色。 C、MSTP 域边缘端口是指位于 MST 域的边…

QT实战项目之音乐播放器

项目效果演示 myMusicShow 项目概述 在本QT音乐播放器实战项目中&#xff0c;开发环境使用的是QT Creator5.14版本。该项目实现了音乐播放器的基本功能&#xff0c;例如开始播放、停止播放、下一首播放、上一首播放、调节音量、调节倍速、设置音乐播放模式等。同时还具备搜索功…

SPR系列单点激光测距传感器|模组之RS485串口调试说明

SPR系列单点激光测距传感器|模组是一款近程红外测距传感器&#xff0c;测距距离可达0-10米&#xff0c;可用于对物体进行非接触式距离测量&#xff0c;其应用场景十分广泛工业自动化&#xff1a;在生产线、传送带等工业自动化场景中&#xff0c;可以使用红外测距传感器进行物体…

Git安装及配置

Git安装 在你开始使用 Git 前,需要将它安装在你的计算机上。 即便已经安装,最好将它升级到最新的版本。 你可以通过软件包或者其它安装程序来安装,或者下载源码编译安装。 下面,我们将会介绍不同操作系统上 Git 的安装方法。 在 Windows 上安装 在 Windows 上安装 Git 的…

LangChain基础知识

这篇文档介绍了LangChain大模型应用开发框架的入门知识和核心内容&#xff0c;包括LangChain是什么、为什么需要它、典型使用场景、基础概念与模块化设计等。同时&#xff0c;还详细阐述了该框架的核心模块如标准化的大模型抽象、大模型应用的最佳实践、赋予应用记忆的能力、框…

JDBC |封装JDBCUtils|PreparedStatement|事务|批处理|数据库连接池| Blob类型数据的读写|Apache—DBUtils简介

一.概述 在Java中&#xff0c;数据库存取技术可分为如下几类&#xff1a; JDBC直接访问数据库JDO技术&#xff08;Java Data Object&#xff09;第三方O/R工具&#xff0c;如Hibernate, Mybatis 等 JDBC是java访问数据库的基石&#xff0c;JDO, Hibernate等只是更好的封装了J…

计算多图的等价无向图的邻接链表表示

计算多图的等价无向图的邻接链表表示 摘要:一、引言二、算法思路三、伪代码实现四、C代码实现五、算法分析六、结论摘要: 在图论中,多图(Multigraph)是一种允许边重复以及存在自循环边(即一个顶点到其自身的边)的图。给定一个多图的邻接链表表示,本文旨在探讨如何构造…