zoukankan      html  css  js  c++  java
  • spring-security使用-登录(一)

    Form表单登录

    默认登录

    1.pom配置

      <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>

    2.java类

    @RestController
    public class HelloController {
        @GetMapping("/hello")
        public String hello() {
            return "hello";
        }
    }

    3.启动项目

    可以发现日志打印了了密码默认是user用户

    3.访问

    localhost:8080/hello 重定向到了http://localhost:8080/login

    3.输入用户名密码再访问

    用户名:user 密码:7e6a1360-1115-4eb6-8e17-5308164e5b26

    4.为什么是给user生成的密码如何生成

    查看UserDetailsServiceAutoConfiguration此类

    org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration#getOrDeducePassword

     private String getOrDeducePassword(User user, PasswordEncoder encoder) {
            //user获得密码
    String password
    = user.getPassword(); if (user.isPasswordGenerated()) {
    //这里就是控制台打印的日志 logger.info(String.format(
    "%n%nUsing generated security password: %s%n", user.getPassword())); } return encoder == null && !PASSWORD_ALGORITHM_PATTERN.matcher(password).matches() ? "{noop}" + password : password; }

    5.我们再看User

    这里一目了然 默认是user和uuid生成用户名和密码,我们可以通过在配置文件配置修改

    spring.security.user.name=liqiang
    spring.security.user.password=liqiang
    @ConfigurationProperties(
            prefix = "spring.security"//说明我们可以通过配置文件配置密码和user
    )
    public class SecurityProperties {
        private SecurityProperties.User user = new SecurityProperties.User();
    
        public static class User {
            //用户 默认user
            private String name = "user";
            //使用uuid生成密码
            private String password = UUID.randomUUID().toString();
            private List<String> roles = new ArrayList();
            private boolean passwordGenerated = true;
    
            public void setPassword(String password) {
                //密码是否不为空
                if (StringUtils.hasLength(password)) {
                    //标签打为false  后面if (user.isPasswordGenerated()) 打印日志
                    this.passwordGenerated = false;
                    this.password = password;
                }
            }
        }
    }

    内存中多用户配置

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        /**
         * 对密码进行加密的实例
         * @return
         */
        @Bean
        PasswordEncoder passwordEncoder() {
            /**
             * 不加密所以使用NoOpPasswordEncoder
             * 更多可以参考PasswordEncoder 的默认实现官方推荐使用: BCryptPasswordEncoder,BCryptPasswordEncoder
             */
            return NoOpPasswordEncoder.getInstance();
        }
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            /**
             *  inMemoryAuthentication 开启在内存中定义用户
             *  多个用户通过and隔开
             */
            auth.inMemoryAuthentication()
                    .withUser("liqiang").password("liqiang").roles("admin")
                     .and()
                    .withUser("admin").password("admin").roles("admin");
        }
    }
    PasswordEncoder为防止密码泄露,数据库保存的是加密的密码,然后前端登录传过来加密后根据数据库的进行匹配 我们可以自己实现和用默认的

    自定义登录页面

    security的默认登录页面不能满足我们需求,我们大多数场景都需要自定义登录页面

    1.增加自定义登录页面html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!--会get请求login.html 提交则是post login--> <form action="/login.html" method="post"> <div class="input"> <label for="name">用户名</label> <input type="text" name="username" id="name"> <span class="spin"></span> </div> <div class="input"> <label for="pass">密码</label> <input type="password" name="password" id="pass"> <span class="spin"></span> </div> <div class="button login"> <button type="submit"> <span>登录</span> <i class="fa fa-check"></i> </button> </div> </form> </body> </html>

    2.配置自定义html页面路径

     /**
         * 对于不需要授权的静态文件放行
         *
         * @param web
         * @throws Exception
         */
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()//form表单的方式
                    .loginPage("/login.html")//登录页面路径
                    .permitAll()//不拦截
                    .and()
                    .csrf()//记得关闭
                    .disable();
        }

    3.再次访问就是显示自定义的登录页面

    自定义登录请求地址

    1.如果我们引入security 什么都不做那么

    get http://localhost:8080/login  登录页面

    post  http://localhost:8080/login 登录请求

    我们前面自定义了登录页面请求但是请求地址action配置的是否可以自定义为其他

    <form action="/login.html" method="post">

    2.我们可以通过后台配置

    @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login.html")//登录页面路径
                    .loginProcessingUrl("/doLogin")
                    .permitAll()//不拦截
                    .and()
                    .csrf()//记得关闭
                    .disable();
        }

    我们的form表单action配置就可以改为

    <form action="/doLogin" method="post">

    登录url自定义和处理登录的源码处

    默认配置是在哪里配置的呢可以看org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer

        public FormLoginConfigurer() {
    //<1>调用父类的 详情看里面
    super(new UsernamePasswordAuthenticationFilter(), (String)null);
    //用户名密码默认参数名
    this.usernameParameter("username"); this.passwordParameter("password"); }

    <1>org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer#AbstractAuthenticationFilterConfigurer()

    protected AbstractAuthenticationFilterConfigurer(F authenticationFilter, String defaultLoginProcessingUrl) {
            
    <2>
    this(); this.authFilter = authenticationFilter; if (defaultLoginProcessingUrl != null) { this.loginProcessingUrl(defaultLoginProcessingUrl); } }

    <2>org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer.AbstractAuthenticationFilterConfigurer

      protected AbstractAuthenticationFilterConfigurer() {
            this.defaultSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler();
            this.successHandler = this.defaultSuccessHandler;
    //可以看到默认是login
    this.setLoginPage("/login"); }

    4.默认登录请求路径的源码处

    org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer#init

     public void init(H http) throws Exception {
    //<1>调用了父类的init
    super.init(http); this.initDefaultLoginFilter(http); }

    <1>org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer#init

        public void init(B http) throws Exception {
    <2>
    this.updateAuthenticationDefaults(); this.updateAccessDefaults(http); this.registerDefaultAuthenticationEntryPoint(http); }

    <2>

    org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer#updateAuthenticationDefaults

    protected final void updateAuthenticationDefaults() {
           //没有手动设置 则默认用loginPage
            if (this.loginProcessingUrl == null) {
                this.loginProcessingUrl(this.loginPage);
            }
    
            if (this.failureHandler == null) {
                this.failureUrl(this.loginPage + "?error");
            }
    
            LogoutConfigurer<B> logoutConfigurer = (LogoutConfigurer)((HttpSecurityBuilder)this.getBuilder()).getConfigurer(LogoutConfigurer.class);
            if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {
                logoutConfigurer.logoutSuccessUrl(this.loginPage + "?logout");
            }
    
        }

    自定义登录用户名密码参数

    1.默认情况下 用户名为username 密码为password为必须

      <div class="input">
            <label for="name">用户名</label>
            <input type="text" name="username" id="name">
            <span class="spin"></span>
        </div>
        <div class="input">
            <label for="pass">密码</label>
            <input type="password" name="password" id="pass">
            <span class="spin"></span>
        </div>

    2.我们可以通过配置修改

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login.html")//登录页面路径
                    .loginProcessingUrl("/doLogin")
                    .usernameParameter("loginName")
                    .passwordParameter("loginPassword")
                    .permitAll()//不拦截
                    .and()
                    .csrf()//记得关闭
                    .disable();
        }
    }

    自定义登录用户名密码参数源码处

    org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer

    public FormLoginConfigurer() {
            super(new UsernamePasswordAuthenticationFilter(), (String)null);
    //设置参数名字
    this.usernameParameter("username"); this.passwordParameter("password"); }

    2.可以发现最终是调用UsernamePasswordAuthenticationFilter对象的set方法写入

    org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer#usernameParameter

    org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer#passwordParameter

     public FormLoginConfigurer<H> usernameParameter(String usernameParameter) {
    ((UsernamePasswordAuthenticationFilter)
    this.getAuthenticationFilter()).setUsernameParameter(usernameParameter); return this; } public FormLoginConfigurer<H> passwordParameter(String passwordParameter) { ((UsernamePasswordAuthenticationFilter)this.getAuthenticationFilter()).setPasswordParameter(passwordParameter); return this; }

    3.fitler内部根据此配置去获取用户名密码

    org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            if (this.postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
            } else {
    //<1> String username
    = this.obtainUsername(request); String password = this.obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } }

    <1>

     @Nullable
        protected String obtainPassword(HttpServletRequest request) {
    //通过我们配置的名字获取
    return request.getParameter(this.passwordParameter); } @Nullable protected String obtainUsername(HttpServletRequest request) {
    //通过我们配置的名字获取
    return request.getParameter(this.usernameParameter); }

    自定义登录跳转

    1.security可通过defaultSuccessUrl、successForwardUrl指定登录成功跳转页面

    defaultSuccessUrl 如果是通过访问其他页面无权访问重定向到登录页面登录,登录成功则跳转到来源页面,如果是直接访问登录页面则直接跳转到指定url

    successForwardUrl 登录成功不管来源直接跳转到指定页面 

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login.html")//登录页面路径
                    .loginProcessingUrl("/doLogin")
                    .usernameParameter("loginName")
                    .passwordParameter("loginPassword")
                    .defaultSuccessUrl("/index")
                    .successForwardUrl("/index")
                    .permitAll()//不拦截
                    .and()
                    .csrf()//记得关闭
                    .disable();
        }

    自定义登录失败跳转

    failureForwardUrl 登录失败服务器重定向

    failureUrl 登录失败前端302重定向地址

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login.html")//登录页面路径
                    .loginProcessingUrl("/doLogin")
                    .usernameParameter("loginName")
                    .passwordParameter("loginPassword")
                    .defaultSuccessUrl("/index")
                    .successForwardUrl("index")
                    .failureForwardUrl("/loginFail")
                    .failureUrl("/login.html")
                    .permitAll()//不拦截
                    .and()
                    .csrf()//记得关闭
                    .disable();
        }
    }

    注销登录

    .and()
    .logout()
    .logoutUrl("/logout")//自定义注销地址
    .logoutRequestMatcher(new AntPathRequestMatcher("/logout","POST"))  //自定义请求方式
    .logoutSuccessUrl("/login.html") //注销后跳转页面
    .deleteCookies()//清除cookie
    .clearAuthentication(true)//清除权限相关
    .invalidateHttpSession(true)//清除session
    .permitAll()
    .and()

    ajax登录+验证码登录

    1.自定义登录处理器

    public class CustomizeUsernamePasswordAuthenticationFilter 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());
            }
            //从session获得验证码
            String verify_code = (String) request.getSession().getAttribute("verify_code");
            //判断是否是json post请求 如果不是则走父类的form登录
            if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
                Map<String, String> loginData = new HashMap<>();
                try {
                    //解析bodyjson数据转为Map
                    loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                } catch (IOException e) {
                    throw new AuthenticationServiceException("系统异常");
                }
                String code = loginData.get("code");
                //检查验证码
                checkCode(response, code, verify_code);
                //获得用户输入的用户名和密码 如果是form登录的话上面配置的用户名key和密码key就不会起效 用默认的
                String username = loginData.get(getUsernameParameter());
                String password = loginData.get(getPasswordParameter());
                if (username == null) {
                    username = "";
                }
                if (password == null) {
                    password = "";
                }
                username = username.trim();
                //模拟父类的实现
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                        username, password);
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            } else {
                checkCode(response, request.getParameter("code"), verify_code);
                //如果是form登录直接使用父类的
                return super.attemptAuthentication(request, response);
            }
        }
        public void checkCode(HttpServletResponse resp, String code, String verify_code) {
            if (code == null || verify_code == null || "".equals(code) || !verify_code.toLowerCase().equals(code.toLowerCase())) {
                //验证码不正确
                throw new AuthenticationServiceException("验证码不正确");
            }
        }
    }

    2.初始化登录处理器并重写登录成功和登录失败的逻辑

    com.liqiang.demo.configs.SecurityConfig#initLoginFilter

     public Filter initLoginFilter() throws Exception {
            CustomizeUsernamePasswordAuthenticationFilter customizeUsernamePasswordAuthenticationFilter= new CustomizeUsernamePasswordAuthenticationFilter();
            /**
             * 授权成功处理器 可以看父类里面默认就是这2个
             */
            SavedRequestAwareAuthenticationSuccessHandler handler=new SavedRequestAwareAuthenticationSuccessHandler();
            AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
            customizeUsernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
                @Override
                public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                    //此处我们做了兼容 如果是json 则响应json 否则走form默认的逻辑
                    if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        Map<String,Object> result=new HashMap<>();
                        result.put("code",200);
                        result.put("message","登录成功");
                        String s = new ObjectMapper().writeValueAsString(result);
                        out.write(s);
                        out.flush();
                        out.close();
                    }else{
                        //表单登录委托给默认的处理器
                        handler.onAuthenticationSuccess(request,response,authentication);
                    }
                }
            });
            //授权失败处理器
            customizeUsernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
                @Override
                public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                    //此处我们做了兼容 如果是json 则响应json 否则走form默认的逻辑
                    if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        Map<String,Object> result=new HashMap<>();
                        result.put("code",200);
                        result.put("message","登录失败");
                        if (exception instanceof LockedException) {
                            result.put("code",901);
                            result.put("message","账户被锁定,请联系管理员!");
                        } else if (exception instanceof CredentialsExpiredException) {
                            result.put("code",902);
                            result.put("message","密码过期,请联系管理员!");
                        } else if (exception instanceof AccountExpiredException) {
                            result.put("code",903);
                            result.put("message","账户过期,请联系管理员!");
                        } else if (exception instanceof DisabledException) {
    
                            result.put("code",904);
                            result.put("message","账户被禁用,请联系管理员!");
                        } else if (exception instanceof BadCredentialsException) {
                            result.put("code",905);
                            result.put("message","用户名或者密码输入错误,请重新输入!");
                        }
                        out.write(new ObjectMapper().writeValueAsString(result));
                        out.flush();
                        out.close();
                    }else{
                        //表单登录委托给默认的处理器
                        failureHandler.onAuthenticationFailure(request,response,exception);
                    }
                }
            });
            customizeUsernamePasswordAuthenticationFilter.setAuthenticationManager(super.authenticationManagerBean());
            //拦截处理的url
            customizeUsernamePasswordAuthenticationFilter.setFilterProcessesUrl("/doLogin");
            return customizeUsernamePasswordAuthenticationFilter;
        }

    3.替换默认的登录处理器

       @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login.html")//登录页面路径
                    .loginProcessingUrl("/doLogin")
                    .usernameParameter("loginName")
                    .passwordParameter("loginPassword")
                    .defaultSuccessUrl("/index")
                    .successForwardUrl("/index")
                    .failureForwardUrl("/loginFail")
                    .failureUrl("/login.html")
                    .permitAll()//不拦截
                    .and()
                    //替换默认的登录处理器 注意form的配置将不起效果 可看formLogin源码 并没有替换 比如 usernameParameter passwordParameter
                    .addFilterAt(initLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                    .csrf()//记得关闭
                    .disable();
        }

    4.测试

    自动登录 

    简单实用

    1.在原来的基础上增加记住密码配置

       @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and().rememberMe()
                    .and()
                    .formLogin()
                    .usernameParameter("loginName")
                    .passwordParameter("loginPassword")
                    .defaultSuccessUrl("/hello")
                    .failureForwardUrl("/loginFail")
                    .failureUrl("/login.html")
                    .permitAll()//不拦截
                    .and()
                    //替换默认的登录处理器 注意顺序 因为下面有给拦截器的属性赋值 比如 usernameParameter passwordParameter
                    .addFilterAt(initLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                    .csrf()//记得关闭
                    .disable();
        }

    原理

    1.在登录时发现提交参数增加了一个remember-me: on

    2.退出浏览器都会默认带上cookie

    Cookie:
    JSESSIONID=5401CED03129ED6CF941CF9812170EAE; remember-me=bGlxaWFuZzoxNjEwMDk0MzgyMDQ2OjVlODZkOTc2OTY0M2UzMmZlZjUyNTgzYWU0NjhhNTJh

    3.将remember-me的值通过base 64解密发现

    值为:  liqiang:1610094382046:5e86d9769643e32fef52583ae468a52a

    第一个为用户名 第二个为过期时间 第三个为加密key 

    4.remomber-me生成源码

    org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices

     public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
            //从登录Authentication 获取用户名和密码
            String username = this.retrieveUserName(successfulAuthentication);
            String password = this.retrievePassword(successfulAuthentication);
            if (!StringUtils.hasLength(username)) {
                this.logger.debug("Unable to retrieve username");
            } else {
                //因为登录成功密码有可能被其他filter擦除 这里如果没有再查一次
                if (!StringUtils.hasLength(password)) {
                    UserDetails user = this.getUserDetailsService().loadUserByUsername(username);
                    password = user.getPassword();
                    if (!StringUtils.hasLength(password)) {
                        this.logger.debug("Unable to obtain password for user: " + username);
                        return;
                    }
                }
                //获取过期时间秒默认是 应该是可配的  默认是1209600
                int tokenLifetime = this.calculateLoginLifetime(request, successfulAuthentication);
                long expiryTime = System.currentTimeMillis();
                //当前时间加上过期时间毫秒
                expiryTime += 1000L * (long)(tokenLifetime < 0 ? 1209600 : tokenLifetime);
                //算出加密值
                String signatureValue = this.makeTokenSignature(expiryTime, username, password);
                // 用户名+:+过期时间+:密码+:+加密值 写入cookie 并设置过期时间
                this.setCookie(new String[]{username, Long.toString(expiryTime), signatureValue}, tokenLifetime, request, response);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Added remember-me cookie for user '" + username + "', expiry: '" + new Date(expiryTime) + "'");
                }
    
            }
        }
        protected String makeTokenSignature(long tokenExpiryTime, String username, String password) {
            //用户名+:+过期时间+:密码+:秘钥 这个getKey是uuid  每次重启 都需要重新登录 我们可以配置写死
    String data = username + ":" + tokenExpiryTime + ":" + password + ":" + this.getKey(); MessageDigest digest; try { //进行md5加密 digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException var8) { throw new IllegalStateException("No MD5 algorithm available!"); } return new String(Hex.encode(digest.digest(data.getBytes()))); }

    key配置

     protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and().rememberMe()
                    .key("system")
                    .and()
                    .formLogin()
    //                .loginPage("/login.html")//登录页面路径
    //                .loginProcessingUrl("/doLogin")
                    .usernameParameter("loginName")
                    .passwordParameter("loginPassword")
                    .defaultSuccessUrl("/hello")
                    .failureForwardUrl("/loginFail")
                    .failureUrl("/login.html")
                    .permitAll()//不拦截
                    .and()
                    //替换默认的登录处理器 注意顺序 因为下面有给拦截器的属性赋值 比如 usernameParameter passwordParameter
                    .addFilterAt(initLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                    .csrf()//记得关闭
                    .disable();
        }

    5.校验

    org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter

      public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest)req;
            HttpServletResponse response = (HttpServletResponse)res;
            if (SecurityContextHolder.getContext().getAuthentication() == null) {
                //先调用rememberMeServices 的autoLogin 取出cookie 进行解析校验有消息
                Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
                if (rememberMeAuth != null) {
                    try {
                        rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
                        SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
                        this.onSuccessfulAuthentication(request, response, rememberMeAuth);
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
                        }
    
                        if (this.eventPublisher != null) {
                            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
                        }
    
                        if (this.successHandler != null) {
                            this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
                            return;
                        }
                    } catch (AuthenticationException var8) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '" + rememberMeAuth + "'; invalidating remember-me token", var8);
                        }
    
                        this.rememberMeServices.loginFail(request, response);
                        this.onUnsuccessfulAuthentication(request, response, var8);
                    }
                }
    
                chain.doFilter(request, response);
            } else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
                }
    
                chain.doFilter(request, response);
            }
    
        }

    持久化令牌

    简单使用

    好处是,每次获取令牌都是从数据库拿,如果有别的地方登陆 token就会改变,原有用户的token就校验不过。让用户感知账号被别的地方登陆

    参考的org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl 实现 由查询数据库改为查redis

    @Component
    public class RedisPersistentTokenRepository implements PersistentTokenRepository {
        TokenRedisRepository tokenRedisRepository;
        PersistentRememberMeTokenConvert persistentRememberMeTokenConvert;
        @Override
        public void createNewToken(PersistentRememberMeToken persistentRememberMeToken) {
            PersistentRememberMeTokenRo persistentRememberMeTokenRo=persistentRememberMeTokenConvert.toRo(persistentRememberMeToken);
            //保存到redis hash方式 key为token:{series}
            tokenRedisRepository.save(persistentRememberMeToken);
        }
    
        @Override
        public void updateToken(String series, String tokenValue, Date lastUsed) {
            PersistentRememberMeTokenRo persistentRememberMeTokenRo=tokenRedisRepository.findOne(series);
            persistentRememberMeTokenRo.setDate(lastUsed);
            persistentRememberMeTokenRo.setTokenValue(tokenValue);
            //保存到redis hash方式 key为token:{userName}
            tokenRedisRepository.save(persistentRememberMeToken);
        }
    
        @Override
        public PersistentRememberMeToken getTokenForSeries(String series) {
            PersistentRememberMeTokenRo persistentRememberMeTokenRo=tokenRedisRepository.findOne(series);
            return persistentRememberMeTokenConvert.toPersistentRememberMeToken(persistentRememberMeTokenRo);;
        }
    
        @Override
        public void removeUserTokens(String series) {
            tokenRedisRepository.del(s);
        }
    }

    配置

     http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .rememberMe()
                    .tokenRepository(getApplicationContext().getBean(RedisPersistentTokenRepository.class))
                    .key("system")
                    .and()
                    .formLogin()
    //                .loginPage("/login.html")//登录页面路径
    //                .loginProcessingUrl("/doLogin")
                    .usernameParameter("loginName")
                    .passwordParameter("loginPassword")
                    .defaultSuccessUrl("/hello")
                    .failureForwardUrl("/loginFail")
                    .failureUrl("/login.html")
                    .permitAll()//不拦截
                    .and()
                    //替换默认的登录处理器 注意顺序 因为下面有给拦截器的属性赋值 比如 usernameParameter passwordParameter
                    .addFilterAt(initLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                    .csrf()//记得关闭
                    .disable();

    源码

    1.写入

    org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices

      protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
            String username = successfulAuthentication.getName();
            this.logger.debug("Creating new persistent login for user " + username);
            //创建persistentTokend对象
            PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());
            try {
                //持久化
                this.tokenRepository.createNewToken(persistentToken);
                //写入cookie
                this.addCookie(persistentToken, request, response);
            } catch (Exception var7) {
                this.logger.error("Failed to save persistent token ", var7);
            }
    
        }

    2.校验

    重写了父类的此方法

    org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices#processAutoLoginCookie
  • 相关阅读:
    调试相关blogs收集
    union和union all
    V$SQLAREA
    Oracle Access和filter的区别
    Oracle 复合索引设计原理——前缀性和可选性
    经济学原理---8应用:税收的代价--- 读书笔记
    经济学原理---7 消费者.生产者与市场效率--- 读书笔记
    经济学原理---6 供给.需求与政府政策--- 读书笔记
    经济学原理---5 弹性及其应用 --- 读书笔记
    CURL---常见问题
  • 原文地址:https://www.cnblogs.com/LQBlog/p/14113755.html
Copyright © 2011-2022 走看看