SpringSecurity (六) RememberMe登录

一.项目配置

延用第五节的配置,依旧使用json的返回返回值

二.RememberMe的作用

1.默认可以所有接口访问,需要在securityConfig里配置securityFilterChain
2.加入了.remember()就是默认加入了rememberMe过滤器链rememberFilterChain,一共有35个过滤器链,不配置默认14个启用

在这里插入图片描述
在这里插入图片描述

三.securityConfig里配置securityFilterChain增加配置rememberFilterChain

    package com.huang.springsecurity.comments.config;

    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.huang.springsecurity.comments.result.RespBean;
    import com.huang.springsecurity.model.User;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.*;
    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.WebSecurityCustomizer;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.*;
    import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
    import org.springframework.security.web.SecurityFilterChain;

    import java.io.PrintWriter;
    import java.util.HashMap;
    import java.util.Map;

    @Configuration
    public class SecurityConfig {





        @Bean
        PasswordEncoder passwordEncoder() {
            //这个表示使用明文密码
//        return NoOpPasswordEncoder.getInstance();
            //表示使用 bcrypt 做密码加密
//        return new BCryptPasswordEncoder();
            String encodingId = "bcrypt";
            Map<String, PasswordEncoder> encoders = new HashMap();
            encoders.put(encodingId, new BCryptPasswordEncoder(12));
            encoders.put("ldap", new LdapShaPasswordEncoder());
            encoders.put("MD4", new Md4PasswordEncoder());
            encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
            encoders.put("noop", NoOpPasswordEncoder.getInstance());
            encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
            encoders.put("scrypt", new SCryptPasswordEncoder());
            encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
            encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
            encoders.put("sha256", new StandardPasswordEncoder());
            encoders.put("argon2", new Argon2PasswordEncoder());
            return new DelegatingPasswordEncoder(encodingId, encoders);
        }


        /**
         * 给登录页面放行
         * Spring Security 给一个地址放行,有两种方式:
         * 1. 被放行的资源,不需要经过 Spring Security 过滤器链(静态资源一般使用这种)。
             * 2. 经过 Spring Security 过滤器链,但是不拦截(如果是一个接口想要匿名访问,一般使用这种)。
         * <p>
         * 下面这种方形方式是第一种
         *
         * @return
         */

        @Bean
        WebSecurityCustomizer securityCustomizer() {
            return new WebSecurityCustomizer() {
                @Override
                public void customize(WebSecurity web) {
                    web.ignoring().antMatchers("/login.html");
                }
            };
        }

        /**
         * 自己手动配置安全过滤器链
         *
         * @return
         */
        @Bean
        SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            //开始认证
            http.authorizeRequests()
                    //请求路径如果是 /login.html,则这个请求可以匿名通过
                    .antMatchers("/login.html").anonymous()
                    //所有的请求,类似于 shiro 中的 /**
                    .anyRequest()
                    //必须要认证之后才能访问,类似于 shiro 中的 authc
                    .authenticated()
                    .and()
                    //开始配置登录表单
                    .formLogin()
                    //配置登录页面,如果访问了一个需要认证之后才能访问的页面,那么就会自动跳转到这个页面上来
                    .loginPage("/login.html")
                    //配置处理登录请求的接口,本质上其实就是配置过滤器的拦截规则,将来的登录请求就会在过滤器中被处理
                    .loginProcessingUrl("/doLogin")
                    //配置登录表单中用户名的 key
                    .usernameParameter("username")
                    //配置登录表单中用户密码
                    .passwordParameter("password")
                    //配置登录成功后的跳转地址
//                .defaultSuccessUrl("/hello")
//                .failureUrl("/login.html")
                    //登录成功处理器
                    //req:当前请求对象
                    //resp:当前响应对象
                    //auth:当前认证成功的用户信息
                    .successHandler((req, resp, auth) -> {
                        resp.setContentType("application/json;charset=utf-8");
                        User principal = (User) auth.getPrincipal();
                        principal.setPassword(null);
                        RespBean respBean = RespBean.ok("登录成功", principal);
                        String s = new ObjectMapper().writeValueAsString(respBean);
                        resp.getWriter().write(s);
                    })
                    //登录失败的回调
                    .failureHandler((req, resp, e) -> {
                        resp.setContentType("application/json;charset=utf-8");
                        //登录失败可能会有多种原因
                        RespBean respBean = RespBean.error("登录失败");
                        if (e instanceof BadCredentialsException) {
                            respBean.setMsg("用户名或者密码输入错误,登录失败");
                        } else if (e instanceof UsernameNotFoundException) {
                            //默认情况下,这个分支是不会进来的,Spring Security 自动隐藏了了这个异常,如果系统中发生了 UsernameNotFoundException 会被自动转为 BadCredentialsException 异常然后抛出来
                        } else if (e instanceof LockedException) {
                            //如果 com.qfedu.security02.model.User.isAccountNonLocked 方法返回 false,就会进入到这里来
                            respBean.setMsg("账户被锁定,登录失败");
                        } else if (e instanceof AccountExpiredException) {
                            //com.qfedu.security02.model.User.isAccountNonExpired
                            respBean.setMsg("账户过期,登录失败");
                        } else if (e instanceof CredentialsExpiredException) {
                            respBean.setMsg("密码过期,登录失败");
                        } else if (e instanceof DisabledException) {
                            respBean.setMsg("账户被禁用,登录失败");
                        }
                        ObjectMapper om = new ObjectMapper();
                        String s = om.writeValueAsString(respBean);
                        PrintWriter out = resp.getWriter();
                        out.write(s);
                    })

                    
                    .and()
                    //默认情况下,开启了 RememberMe 之后,所有的接口都可以通过 RememberMe 登录之后访问
                    //本质上,这个方法就是向 Spring Security 过滤器链中添加了一个过滤器 RememberMeAuthenticationFilter
                    .rememberMe()
                    .key("huang")
                   
                   
                    .and()
                    //关闭 csrf 防御机制,这个 disable 方法本质上就是从 Spring Security 的过滤器链上移除掉 csrf 过滤器
                    .csrf().disable()
                    .exceptionHandling()
                    //如果用户未登录就访问某一个页面,就会触发当前方法
                    .authenticationEntryPoint((req, resp, authException) -> {
                        resp.setContentType("application/json;charset=utf-8");
                        RespBean respBean = RespBean.error("尚未登录,请登录");
                        String s = new ObjectMapper().writeValueAsString(respBean);
                        resp.getWriter().write(s);
                    });

            return http.build();
        }
    }

增加的RememberMe配置为

	.and()
 //默认情况下,开启了 RememberMe 
 //本质上,这个方法就是向 Spring Security 过滤器链中添加了一个过滤器 RememberMeAuthenticationFilter
.rememberMe()
.key("huang")
                   

四. RememberMe的登录返回值

开启 RememberMe 之后,登录成功后,服务端会响应一个 rm 字符串回来:emhhbmdzYW46MTY1OTQwMzM3NzE0ODowNjczYzdlYWFlNjE5MWU1YzY1MDYyOWRhMWUwZWQ5Zg ,这是一个 base64 编码之后的字符串,解码之后,分为三部分:

  • 用户名
  • 时间戳
  • 加密的字符串(根据用户名+用户密码+时间戳+key加密生成的字符串,不可解密)

以后每次请求的时候,都会自动携带上这个 Cookie,服务端收到 Cookie 之后,会解析出来用户名和时间戳,通过时间戳就能判断出 Cookie 是否已经过期,没有过期的话,根据用户名查询出用户密码,然后根据和用户名、用户密码、时间戳、key 进行加密,将加密后的字符串跟 Cookie 中的第三部分进行比较。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值