SpringSecurity系列,第二章:RememberMe 和 异常处理

SpringSecurity系列,第二章:RememberMe 和 异常处理

一、RememberMe

RememberMe这个功能,是为了方便用户在下次登录时直接登录。避免再次输入 用户名 和 密码。

下面我们记录下如何使用RememberMe这个功能的。

1、修改login.html页面

添加remember-me,注意:字段的name必须是remember-me

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
    <h1>登陆</h1>
    <form method="post" action="/login">
        <div>
            用户名:<input type="text" name="username">
        </div>
        <div>
            密码:<input type="password" name="password">
        </div>
        <div>
            <label><input type="checkbox" name="remember-me">记住密码</label>
        </div>
        <div>
            <button type="submit">登陆</button>
            <button type="reset">重置</button>
        </div>
    </form>
</body>
</html>

2、两种存储登录信息方式

2.1、Cookie存储

这种方式只需在配置类中的Configure()方法添加rememberMe()即可

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()

                .formLogin()
                    .loginPage("/login.html") //用户未登录时,访问资源都跳转到该页面,登录页面
                    .loginProcessingUrl("/login")//登录表单,form中的action地址,也就是处理认证请求的路径
                    .usernameParameter("username")//登录表单,form中用户名input框的name名,默认是username
                    .passwordParameter("password")//登录表单,form中密码input框的password名,默认是password
                    .defaultSuccessUrl("/")//登录成功后,默认跳转的路径
                    .permitAll()

                .and()
                .logout().permitAll()

                .and()
                .rememberMe();//记住账号和密码

        //关闭csrf跨域
        http.csrf().disable();

    }

当我们登录勾选remember-me后,会自动在Cookie中保存一个为remember-me的cookie。

默认有效期为2周,其值是一个加密字符串

2.2、数据库存储

使用COOKIE存储用户信息固然很方便,但是COOKIE毕竟是保存在客户端的,而且cookie的值还与用户名、密码这些敏感数据相关,虽然加密了,但是将其存在客户端,毕竟不太安全

Spring Security还提供了另一种相对安全的机制:在客户端的cookie中,进保存一个无意义的加密串(与用户名、密码等无关),然后在数据库中保存该加密串 与 用户信息的 对应关系,自动登录时,用cookie中的加密串,到数据库中验证,如果通过则自动登录。

原理:

浏览器发起表单登录请求时,当通过UsernamePasswordAuthenticationFilter认证成功后,会经过RememberMeService,在其中有一个TokenRepository,它会生成一个token,首先将token写入到浏览器的cookie中,然后将token、认证成功的用户名写入数据库。

在浏览器进行下次请求时,会经过RememberMeAuthenticationFilter,它会读取cookie中的token,交给RememberMeService从数据库中查询记录。如果存在记录,会读取用户名并去调取UserDetailsService,获取用户信息,并将用户信息放到Spring Security中,实现自动登录。

RememberMeAuthenticationFilter在整个SpringSecurity的过滤链中是比较靠后的,也就是说在传统的登录方式都无法通过后才会使用自动登录。

2.2.1、创建表存储Token
DROP TABLE IF EXISTS `persistent_logins`;
CREATE TABLE `persistent_logins` (
  `username` varchar(64) NOT NULL,
  `series` varchar(64) NOT NULL,
  `token` varchar(64) NOT NULL,
  `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.2.2、在配置文件中注入PersistentTokenRepository

在WebSecurityConfig配置类中添加如下:

@Autowired
private DataSource dataSource;

@Bean
public PersistentTokenRepository persistentTokenRepository(){
    JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
    tokenRepository.setDataSource(dataSource);

    return tokenRepository;
}

Configure中配置自动登录

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()

        .formLogin()
        .loginPage("/login.html") //用户未登录时,访问资源都跳转到该页面,登录页面
        .loginProcessingUrl("/login")//登录表单,form中的action地址,也就是处理认证请求的路径
        .usernameParameter("username")//登录表单,form中用户名input框的name名,默认是username
        .passwordParameter("password")//登录表单,form中密码input框的password名,默认是password
        .defaultSuccessUrl("/")//登录成功后,默认跳转的路径
        .permitAll()

        .and()
        .logout().permitAll()

        .and()
        .rememberMe()//记住账号和密码
        .tokenRepository(persistentTokenRepository());//tokenRepositoryBean 去数据库中查找RememberMe信息

    //关闭csrf跨域
    http.csrf().disable();

}

二、异常处理

当我们登录失败是,Spring Security会帮我们跳转到 http://127.0.0.1:8081/login.html?error 。这是Spring Security默认的失败URL。如果我们不手动处理这个异常,这个异常是不会被处理的。

1、常见的异常

一下的异常都是AuthenticationException的子类

UsernameNotFoundException 用户不存在

DisabledException         用户被禁用

BadCredentialsException   坏的凭据

LockedException           账户被锁定

AccountExpireException    账户过期

CredentialsExpiredException凭据过期

...

2、异常处理内部流程

(1)在AbstractAuthenticationProcessingFilter类的doFilter方法中,我们发现,出现异常后,它会调用unsuccessfulAuthentication()方法

(2)在unsuccessfulAuthentication()方法中,它将异常交给了SimpleUrlAuthenticationFailureHandler类的onAuthenticationFailure()方法处理

(3)在onAuthenticationFailure()方法中,首先会判断defaultFailureUrl是否存在,

若果不存在,直接返回401 Unauthorized

如果存在,先存储异常saveException,然后判断forwardToDestination ,是否服务器跳转,默认为false,直接重定向到defaultFailureUrl

saveException,则默认将Exception存储到Session中

3、处理异常

由上分析可知,我们需要配置一下defaultFailureUrl。只要指定错误的URL即可。

(1)指定错误URL,在WebSecurityConfig中添加failureUrl

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()

        .formLogin()
        .loginPage("/login.html") //用户未登录时,访问资源都跳转到该页面,登录页面
        .loginProcessingUrl("/login")//登录表单,form中的action地址,也就是处理认证请求的路径
        .usernameParameter("username")//登录表单,form中用户名input框的name名,默认是username
        .passwordParameter("password")//登录表单,form中密码input框的password名,默认是password
        .defaultSuccessUrl("/")//登录成功后,默认跳转的路径
        .failureUrl("/login/error")//登录失败URL
        .permitAll()

        .and()
        .logout().permitAll()

        .and()
        .rememberMe()//记住账号和密码
        .tokenRepository(persistentTokenRepository());//tokenRepositoryBean 去数据库中查找RememberMe信息

    //关闭csrf跨域
    http.csrf().disable();

}

(2)Controller中处理异常

@ResponseBody
@GetMapping("/login/error")
public String loginError(HttpServletRequest req, HttpServletResponse resp){
    AuthenticationException excp = 
        (AuthenticationException) req.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
    return "异常信息为:"+excp.getMessage();
}

展开阅读全文
打赏
1
4 收藏
分享
加载中
更多评论
打赏
0 评论
4 收藏
1
分享
返回顶部
顶部