SpringBoot集成Spring Security(3)—— 自动登录1

一、前言

        本篇文章来看下利用Spring Security实现自动登录功能,并且简单了解其内部细节。
        Spring Security自动登录实现本质其实也是利用cookie,并且对于cookie的数据可以用数据库保存(不是直接保存cookie字符串)。

二、无持久化方式

1、代码

        首先来看下没有持久化cookie的实现,其实很简单,只要在页面增加一个自动登录的复选框,然后在SecurityConfig类增加对应的配置即可。
(1)login.html

<form action="/doLogin" method="post">
    <input name="username"/>
    <input name="password" type="password"/>
    <input name="remember-me" type="checkbox"/>记住我
    <button type="submit">登录</button>
</form>

PS:“记住我”复选框的name属性Spring Security默认为remember-me,当然这个可以自行设定。

(2)SecurityConfig.java

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService loginUserDetailsService;

    @Autowired
    DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable() // CSRF(跨站请求伪造)禁用,默认开启,会检测请求中是否包含令牌,没有则拒绝并返回403
                .authorizeRequests()
                // 对静态资源放行
                .mvcMatchers(HttpMethod.GET,
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js")
                .permitAll()
                // 指定可匿名访问的路径
                .mvcMatchers("xxx","zzz/yyy").anonymous()
                .anyRequest().authenticated()// 除了上面其他都必须鉴权
                .and()
                .formLogin().loginPage("/loginPage")// 未认证时访问跳转登录页面
                .loginProcessingUrl("/doLogin")// 表单登录url设置,默认/login
                // 登录成功跳转
                .defaultSuccessUrl("/main",true)
                .permitAll()
                .and()
                .logout().permitAll()
                .and()
                .rememberMe() // 自动登录;             
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(loginUserDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(charSequence.toString());
            }
        };
    }
}

那这里只需要增加上rememberMe()即可,运行看效果。
在这里插入图片描述
在这里插入图片描述
可以看到登录请求成功后就会返回cookie,然后跳转到登陆成功main就会携带上cookie,cookie的默认有效时长为14天。
当关掉浏览器再打开然后直接访问/main时,就会直接打开了。

2、细节
(1)相关配置

        首先再介绍几个其他配置,可以自己设置cookie的名字,form表单checkbox的name属性名,还有有效时长,当然还有其他设置项,这里不做说明。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable() // CSRF(跨站请求伪造)禁用,默认开启,会检测请求中是否包含令牌,没有则拒绝并返回403
                .authorizeRequests()
                // 对静态资源放行
                .mvcMatchers(HttpMethod.GET,
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js")
                .permitAll()
                // 指定可匿名访问的路径
                .mvcMatchers("xxx","zzz/yyy").anonymous()
                .anyRequest().authenticated()// 除了上面其他都必须鉴权
                .and()
                .formLogin().loginPage("/loginPage")// 未认证时访问跳转登录页面
                .loginProcessingUrl("/doLogin")// 表单登录url设置,默认/login
                .defaultSuccessUrl("/main").permitAll() // 登录成功跳转url
                .and()
                .logout().permitAll()
                .and()
                .rememberMe() // 自动登录
                .rememberMeCookieName("autoLoginCookie") // cookie的名字,默认remember-me
                .rememberMeParameter("autoLogin") // 对应表单checkbox的name属性
                .tokenValiditySeconds(60); // 设置有效时长,单位秒
    }
(2)cookie内容解析

        再来看下cookie的内容,这个一看就是到是BASE64加密串,所以拿在线解密网站解密下:
在这里插入图片描述可以清楚看到包含3个部分:
1、用户名
2、这个其实就是失效日期对应的时间戳
3、签名
具体内容在TokenBasedRememberMeServices类的onLoginSuccess方法可以查看:

    public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
        String username = this.retrieveUserName(successfulAuthentication);
        String password = this.retrievePassword(successfulAuthentication);
        if (!StringUtils.hasLength(username)) {
            this.logger.debug("Unable to retrieve username");
        } else {
            // 省略...
			// 获取有效时长
            int tokenLifetime = this.calculateLoginLifetime(request, successfulAuthentication); 
            long expiryTime = System.currentTimeMillis();
            // 生成上图中cookie解密后2对应的时间戳
            expiryTime += 1000L * (long)(tokenLifetime < 0 ? 1209600 : tokenLifetime); 
            // 生成3对应的签名(MD5加密)
            String signatureValue = this.makeTokenSignature(expiryTime, username, password); 
            // 生成cookie,添加到esponse
            this.setCookie(new String[]{username, Long.toString(expiryTime), signatureValue}, tokenLifetime, request, response);
            // 省略...

        }
    }

所以这种方式cookie里面包含了用户名、密码,相对来说不是太安全。

(3)自动登录过程

        自动登录的过程可在RememberMeAuthenticationFilter这个过滤器中看到,RememberMeAuthenticationFilter是在UsernamePasswordAuthenticationFilter后执行的,这个顺序可在官网过滤器部分看到。

public class RememberMeAuthenticationFilter extends GenericFilterBean implements ApplicationEventPublisherAware {
	// 省略

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        // 如果拦截请求后会获取Authentication对象,没有认证时为null
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
        	// 在autoLogin中处理自动登录
            Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
           // 省略
        } else {
           // 省略
        }

    }
    // 省略
}

autoLogin方法是在TokenBasedRememberMeServices的父类AbstractRememberMeServices中(rememberMeServices对象为TokenBasedRememberMeServices),源码有删减只看核心部分:

public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
	// 获取请求中的cookie
    String rememberMeCookie = this.extractRememberMeCookie(request);
	// 解析cookie,也就是解析成上面的所说的3部分
    String[] cookieTokens = this.decodeCookie(rememberMeCookie);
    // 这里面就是在进行校验
    user = this.processAutoLoginCookie(cookieTokens, request, response);
             
    }
}

this.processAutoLoginCookie就表示调用TokenBasedRememberMeServices中的processAutoLoginCookie方法,同样源码有删减只看核心部分:

protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
	// 调用UserDetailsService的loadUserByUsername,通过用户名获取数据库用户
	UserDetails userDetails = this.getUserDetailsService().loadUserByUsername(cookieTokens[0]);   
	// 利用数据库用户数据生成签名,此前登录时onLoginSuccess方法也调用此方法生成的签名             
	String expectedTokenSignature = this.makeTokenSignature(tokenExpiryTime, userDetails.getUsername(), 
	// 与cookie中携带的签名比对,一样则验证成功否则失败
	if (!equals(expectedTokenSignature, cookieTokens[2])) {
        throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2] + "' but expected '" + expectedTokenSignature + "'");
    } else {
        return userDetails;
    }
}

        总结一下自动登录过程如下图所示,主要是从RememberMeAuthenticationFilter开始(图中两个过滤器中间还有其他过滤器)
在这里插入图片描述

3、补充

        在配置属性时可能还会看到有博客里面提到key()这个方法,key方法中设置的值到底有何用处?

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
               	.....
                .rememberMe() // 自动登录
                .key("my_key");
    }

        解决这个问题还得看下上面提到过的在生成cookie中签名的方法,TokenBasedRememberMeServices的makeTokenSignature方法:

protected String makeTokenSignature(long tokenExpiryTime, String username, String password) {
        String data = username + ":" + tokenExpiryTime + ":" + password + ":" + this.getKey();

        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException var8) {
            throw new IllegalStateException("No MD5 algorithm available!");
        }

        return new String(Hex.encode(digest.digest(data.getBytes())));
    }

        可以看到第一行这里,生成签名的时候除了用户名、过期时间、密码外还有一个key,这个key是作为防止令牌串改,默认情况下我们没有手动配置key的时候是系统启动时会默认生成,后续登录请求时这个可以不会改变。
        但是有种情况当用户登录一次后浏览器已经存储了cookie,并且在有效期内的某个时间服务器重启了,那么这个时候用户浏览器中的cookie就失效了,得重新登录了。如果说我们自己指定了一个固定的key,那么在cookie有效期内服务器重启也不会有影响了。

SpringBoot集成Activiti是一种常见的工作流引擎的实践。在集成过程中,需要通过ProcessEngine来启动Activiti引擎并获取RuntimeService等服务。这些服务将在Spring容器中进行管理和使用。根据网上的结论,如果使用的是Activiti 6.0版本,并且在Spring 2.0以上的版本中出现问题,可以通过在应用程序的入口类中添加注解@SpringBootApplication(exclude = SecurityAutoConfiguration.class)来解决。这个注解将排除掉Spring Security自动配置,可能会对集成产生冲突的问题进行排除。你可以参考这个链接查看更多关于SpringBoot集成Activiti的示例源码和详细介绍:https://blog.csdn.net/rongbo91/article/details/107972448。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [activit学习(一)——springboot集成activi](https://blog.csdn.net/JHC23/article/details/97001687)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [springboot集成activiti项目Demo源码分享](https://download.csdn.net/download/rongbo91/14109630)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值