Spring Security——基于表单登录认证原理及实现

spring security默认采用basic模式认证。浏览器发送http报文请求一个受保护的资源,浏览器会
弹出对话框让输入用户名和密码。并以用户名:密码的形式base64加密,加入Http报文头部的Authorization
(默认用户名为user,密码则是会在启动程序时后台console里输出,每次都不一样)。后台获取Http报文头
部相关认证信息,认证成功返回相应内容,失败则继续认证。下面会详细介绍具体认证流程。

二、基于表单的认证原理

基本认证流程:
                     SecurityContextPersistenceFilter
                                   ↓
                   AbstractAuthenticationProcessingFilter
                                   ↓
                    UsernamePasswordAuthenticationFilter
                                   ↓
                         AuthenticationManager
                                   ↓
                         AuthenticationProvider
                                   ↓
                          userDetailsService
                                   ↓
                              userDetails
                                   ↓
                                认证通过
                                   ↓
                            SecurityContext
                                   ↓
                          SecurityContextHolder
                                   ↓
                           RememberMeServices
                                   ↓
                      AuthenticationSuccessHandler
SecurityContextPersistenceFilter会校验请求中session是否有SecurityContext,有放SecurityContextHolder
中,返回时校验SecurityContextHolder中是否有securityContext,有放session,从而实现认证信息在多个请求中共
享。
AbstractAuthenticationProcessingFilter中会调自身attemptAuthentication抽象方法的某个实现
UsernamePasswordAuthenticationFilter便是一个,它从请求中获取账号密码后,构造了一个token,此时没有认证,
如下:
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }
随后会调用AuthenticationManager接口中authenticate方法,ProviderManager具体实现,它遍历
provider,调用其supports方法,去匹配之前申明的token,找到provider后调用authenticate方法,
这个方法才开始认证用户信息,表单登录的是DaoAuthenticationProvider其父类进行以下操作:
①调用userDetailsService接口的loadUserByUsername方法获取UserDetails用户信息
②检查UserDetails用户是否可用、是否账户没有过期、是否账户没有被定
③检查UserDetails密码是否正确
④检查UserDetails密码是否没有过期
⑤都通过会重新申明一个token,不过多了权限集合参数如下
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
    Collection authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }
此时用户才被认证通过,上述步骤有一个错了会抛出AuthenticationException中的子异常,下面是其继承图:
Spring Security——基于表单登录认证原理及实现
认证结果返回给AbstractAuthenticationProcessingFilter,将结果放到SecurityContextHolder的
SecurityContext中,若添加了记住我功能又会调用rememberMeServices来实现,最后调用
AuthenticationSuccessHandler接口成功处理。

三、功能实现

在第二部分介绍具体原理,接下来实现自定义登录验证。首先编写一个config类继承WebSecurityConfigurerAdapter
类重写configure方法填写配置
protected void configure(HttpSecurity http) throws Exception {
        http
                /**
                 * 表单登录配置
                 */
                .formLogin() //表单登录
                .loginPage("authentication/login.html") //自定义登录页面
                .loginProcessingUrl("/authentication/form") //与自定义登录页面处理路径一致
//        http.httpBasic()   basic模式弹出框登录
                .successHandler(myAuthenticationSuccessHandler)  //自定义认证成功处理
                .failureHandler(myAuthenticationFailureHandler)  //自定义认证失败处理
                .and()
                /**
                 * 需要认证的请求配置,
                 * 注:最为具体的请求路径放在前面,而最不具体的路径(如anyRequest())放在最后面。
                 * 如果不这样做的话,那不具体的路径配置将会覆盖掉更为具体的路径配置
                 */
                .authorizeRequests()
                //允许这样请求通过,需要将登录所需路径配好,不然会一直重定向
                .antMatchers("/authentication/*").permitAll() 
                .anyRequest().authenticated()//任何请求都需要认证
                .and()
                .csrf().disable(); //关闭csrf防御机制
    }
自定义用户实现UserDetailsService接口重写loadUserByUsername方法
@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new User(username, "123456", true, true,
                true, true, new ArrayList());
    }
这里的User是security自己的,写死了密码为123456,正常应该根据用户名从数据库查出用户信息。当然密码也不可
能为明文,这里推荐使用BCryptPasswordEncoder加密,因为其每次加密都会生成随机盐加入字符串中。需要在之前申
明的config类添加即可
@Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
这四个boolean值分别表示是否可用、是否账户没有过期、是否密码没有过期、是否账户没有被锁定。构造函数
最后一个参数为该用户用哪些接口权限,同样也从数据库查,但是这个权限只会在登录时初始化一次,若我登录后修
改权限,则无法同步,后续介绍解决办法。
    接下来就是两个登录的自定义登录处理,成功的实现AuthenticationSuccessHandler接口,失败的则实现
AuthenticationFailureHandler接口
@Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException{
        response.setContentType("application/json;UTF-8");
        response.getWriter().println("{/"success/":true,/"result/":/"" + "登录成功" + "/"}");
    }
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;UTF-8");
        response.getWriter().println("{/"error/":true,/"message/":/"登录名或者密码错误/"}");
    }
注意成功和失败最后的方法参数是不同的,一个是成功的认证信息,另一个失败抛出的认证异常json串的key可以
根据自身登录页面ajax处理结果的属性来定,必须保持一致。
    页面代码就不复制了,做一个简单html就行。

原文 

https://juejin.im/post/5a40feea5188257d38443360

PS:如果您想和业内技术大牛交流的话,请加qq群(527933790)或者关注微信公众 号(AskHarries),谢谢!

转载请注明原文出处:Harries Blog™ » Spring Security——基于表单登录认证原理及实现

赞 (0)

分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址