spring-security 自定义登陆校验

1.为什么要作自定义登陆页面以及校验
在项目中配置了spring-security的模块的项目中,spring boot会默认帮咱们生成的一个简洁的登陆页面,它会在咱们访问任何请求的时候弹出来spring

spring-security 自定义登陆校验

用户名是默认的:user,密码是须要咱们找到咱们启动项目时的日志,里面会随机生成一个默认的密码
spring-security 自定义登陆校验
输入以后就能够访问到咱们的资源信息了
spring-security 自定义登陆校验
但这种场景给咱们的项目场景确定截然不同了,不论是登陆的页面仍是校验的规则在实际的项目中都是比较复杂的。
这个时候就须要咱们自定义登陆页面以及自定义校验了数据库

2.如何自定义
须要继承WebSecurityConfigurerAdapter这个类,并重写configure方法
2.1 一个简单的基于表单登陆
可是configure有三个方法(从源码可知),分别接收不一样的参数,咱们应该重写哪个呢?
spring-security 自定义登陆校验
回到咱们原来的设想,是想让程序使用表单进行登陆
首先配置一个最简单的,基于表单认证的一个页面配置
这个时候咱们须要覆盖configure(HttpSecurity http)方法浏览器

protected void configure(HttpSecurity http) throws Exception {     
        //用表单登陆,进行身份认证,全部的请求都须要进行身份认证才能够访问
        http.formLogin()  //表单登陆的意思(指定了身份认证的方式)
        //受权
        .and()
        //对请求进行一个受权
        .authorizeRequests()
        .anyRequest().authenticated();//任何请求都须要身份认证
    }

这个时候 启动项目,再次访问请求,会看到一个表单页面(也是spring security提供给咱们的)
spring-security 自定义登陆校验
用户名和密码仍是上面同样,user和控制台输出的密码(每次启动密码都会变)
这个时候观察浏览器上的地址变化会和http.httpBasic()这种方式不太同样,咱们访问的地址是
http://localhost:8080/user/2 在访问的时候会帮咱们强制跳转至:http://localhost:8080/login 页面(登陆页面),在认证成功以后会自动帮咱们重定向xx/user/2 这个连接。ide

2.2Spring Security核心原理(过滤器链)
全部访问服务的请求都会通过spring security它的过滤器,响应也一样会。这些过滤器在项目启动的时候spring boot会自动配进去。post

spring-security 自定义登陆校验

做用:用来认证用户的身份,每一个过滤器负责一种认证方式
对于刚才咱们的登陆而言:对于表单登陆的由UsernamePasswordAuthenticationFilter,对于基本登陆的(也就是http.httpBasic)则由BasicAuthenticationFilter来处理。this

例如:对于表单登陆它会怎么样来判断这个请求会走这个Filter?
对于Filter来讲,它会检查当前的请求中是否有这个Filter所须要的信息,对于UsernamePasswordAuthenticationFilter来讲,首先这个请求是不是登陆请求,请求中带没带用户名(username)和密码(password),若是带了,这会尝试用这个帐户名和密码进行登陆,若是没有带,则会放行,走到下一个Filter中。加密

任何一个Filter成功完成了用户登陆之后会在请求上作一个标记(这个用户认证成功了)3d

最终会到一个FilterSecurityInterceptor过滤器中,是该过滤器链的最后一环。
在这个过滤器中它会决定你当前的请求能不能去访问后面的Controller,依据什么来判断呢?依据咱们configure方法中所配置的。
spring-security 自定义登陆校验
咱们如今的配置是:全部的请求都须要通过身份认证才能访问,此时这个Filter会判断当前的请求通过了前面的某一个Filter的身份认证。调试

针对复杂场景:针对某些请求,只能有VIP用户才能访问,这些规则都会被放在这个FilterSecurityInterceptor里面,这个过滤器会根据这些规则作判断,判断的结果是过仍是不过,不过的话,会根据不一样的缘由抛出不一样的异常。好比 若是没有通过身份认证,则抛出一个没身份认证的一个异常;若是只有VIP才能访问这个请求,那么也须要抛出异常,由于权限不够。
在异常抛出去以后,会有一个异常过滤器来拦截ExceptionTranslationFilter,这个异常过滤器就是用来捕获FilterSecurityInterceptor里面所抛出来的异常,它会根据里面抛出来的异常作响应的处理,若是没有没有登陆则引导用户去登陆等。日志

spring-security 自定义登陆校验
在过滤器链中,除了绿色的过滤器(UsernamePasswordAuthenticationFilter/BasicAuthenticationFilter/...)是能够经过配置来禁用或者启用的,对于其余颜色的过滤器都是不能控制的,必定会在过滤器链上,并且位置是不可变的。

2.3经过调试解析spring security登陆的一个过程
咱们须要在四个类里面打上断点
1.第一个断点(Controller层)
spring-security 自定义登陆校验

2.第二个断点:(FilterSecurityInterceptor层)

spring-security 自定义登陆校验

3.第三个断点:(ExceptionTranslationFilter层)
spring-security 自定义登陆校验

4.第四个断点:(UsernamePasswordAuthenticationFilter层)
spring-security 自定义登陆校验

从UsernamePasswordAuthenticationFilter这个过滤器中,它只会处理/login post的请求
收到请求只会,它会从request中拿到用户名和密码,而后作登陆。

spring-security 自定义登陆校验

发送请求,由于请求的参数中没有username/password参数,直接就到了最后一个过滤器(FilterSecurityInterceptor)由它来判断是否被拦截。
spring-security 自定义登陆校验
由于咱们配置了全部请求都须要进行身份认证,断点进行下一步时,则会抛出来一个异常(ExceptionTranslationFilter
spring-security 自定义登陆校验

spring-security 自定义登陆校验
捕获到的是未受权异常。这个时候前台页面就会回到了登陆页面
spring-security 自定义登陆校验

在登陆页面输入完,正确的帐户名和密码以后,断点会来到
UsernamePasswordAuthenticationFilter
spring-security 自定义登陆校验
它会从request中取到用户名和密码进行校验,校验成功后会继续回到了
FilterSecurityInterceptor)中的super.beforeInvocation(fi)方法,为何呢?由于以前的请求是
http://localhost:8080/user/2 它是这个资源访问(Controller),在登陆成功以后会有个资源的跳转。

spring-security 自定义登陆校验

在doFilter以后就调到咱们本身的Controller中的内容了。
spring-security 自定义登陆校验
所有完成以后,在页面的前台就能够看到请求的信息了。

spring-security 自定义登陆校验

2.4自定义登陆帐户校验
为何须要自定义呢,由于这个咱们的业务场景不只仅在是个简单帐户的校验,须要根据咱们本身的业务逻辑来实现相关的功能。
相关功能点:

用户信息的获取逻辑在spring security中是被封装在一个接口中(UserDetailsService

在此接口中只有一个loadUserByUsername方法
spring-security 自定义登陆校验
根据用户在前台传输过来的用户名来查询用户信息,用户的信息被封装在UserDetails的实现类里面,这个实现类返回之后,作一下响应的处理,校验都经过了 就会把这个用户放到Session里面,就会认为你的登陆成功了。
找不到用户则会抛异常UsernameNotFoundException,也会被响应的处理类接收。

实现代码

  • 处理用户信息的获取(从数据库查询)
@Component
public class MyUserDetailsService implements UserDetailsService{

    private static final Logger logger = LoggerFactory.getLogger(MyUserDetailsService.class);

    //@Autowired
    //private XXService xxService; 

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("根据用户名查询用户信息"+username);
        //查询用户信息
        //xxService.query(username);
        return new User(username, "123456", AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }

}

拿到用户信息以后须要组装成UserDetails对象,可是它自己就是一个接口 应该怎么返回呢?
这个时候须要用到spring security中给咱们提供的一个User对象,这个对象已经实现了UserDetails接口
spring-security 自定义登陆校验
咱们须要用到它的方法
spring-security 自定义登陆校验

第一个参数就是用户名,第二个参数就是密码,第三个参数就是咱们的角色(作受权)。

此时咱们就能够在浏览器上访问咱们的请求地址了:http://localhost:8080/login

帐号随便,密码为123456,输入错误的密码则会被抛出来异常
spring-security 自定义登陆校验

密码正确,则被正常放行。

  • 处理用户校验逻辑(是否冻结,是否过时等)
    常规业务状况须要返回UserDetails的实现类去实现用户的锁定,过时,注销等操做,这些方法是UserDetails中帮咱们定义的,也可使用User实现类。
public class MyUser implements UserDetails {

    private String username;
    private String password;

    private boolean accountNonExpired;
    private boolean accountNonLocked;
    private boolean credentialsNonExpired;
    private boolean enabled;

    public MyUser(String username, String password, boolean accountNonExpired, boolean accountNonLocked,
            boolean credentialsNonExpired, boolean enabled) {
        this.username = username;
        this.password = password;
        this.accountNonExpired = accountNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.credentialsNonExpired = credentialsNonExpired;
        this.enabled = enabled;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

在处理失效的属性时,置为无效;

return new MyUser(username, "123123", true, true, true, false);

再次启动,登陆的用户信息都会是已经失效的用户
spring-security 自定义登陆校验

  • 处理密码加密解密

    处理加密和解密是一个新的接口(PasswordEncoder)。
    org.springframework.security.crypto.password包下面的类。
    spring-security 自定义登陆校验
    加密方法是咱们本身调用的(encode),而解密方法是spring security是自动调用的,根据UserDetails的实现类中的密码是自动调用的。
    配置加密

    BrowserSecurityConfig类中配置密码加密

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    BCryptPasswordEncoder是spring security推荐的一个加密方法,也能够实现本身的加密方法,只须要实现PasswordEncoder接口的实现类便可。

    这个时候咱们的登陆接口则须要修改成:

//常规状况下,passwordEncoder.encode("123123")是注册时须要作的方法,
 //而登陆的时候则只须要传递password便可
        return new MyUser(username, passwordEncoder.encode("123123"), true, true, true, false);

为了更好的观察到 BCryptPasswordEncoder的强大之处,打印出帐号的密码。

String password = passwordEncoder.encode("123123");

 logger.info(username+"的密码是:"+password);

此时咱们启动服务,输入咱们的请求地址。
登陆成功以后能够看到后台会输出 BCryptPasswordEncoder加密后的密码:
spring-security 自定义登陆校验
当咱们使用同一帐户进行反复登陆时继续观察日志
spring-security 自定义登陆校验
会发现,两次的加密后的密码是不一致的。
原理一样的一个密码,随机生成一个盐值,而且在最后生成密码串的时候把随机生成的盐混到这个串里面,每次判断的是能够用随机生成的盐+生成的串去比对,最终来判断密码是否匹配。这样就能够避免同一个密码被反复盗用。

相关文章
相关标签/搜索