JAVA语言之spring-security 自定义登录校验
小标 2019-02-28 来源 : 阅读 972 评论 0

摘要:本文主要向大家介绍了JAVA语言之spring-security 自定义登录校验,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

本文主要向大家介绍了JAVA语言之spring-security 自定义登录校验,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

JAVA语言之spring-security 自定义登录校验

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


用户名是默认的:user,密码是需要我们找到我们启动项目时的日志,里面会随机生成一个默认的密码

输入之后就可以访问到我们的资源信息了

但这种场景给我们的项目场景肯定大相径庭了,不管是登录的页面还是校验的规则在实际的项目中都是比较复杂的。
这个时候就需要我们自定义登录页面以及自定义校验了


2.如何自定义
需要继承WebSecurityConfigurerAdapter这个类,并重写configure方法
2.1 一个简单的基于表单登录
但是configure有三个方法(从源码可知),分别接收不同的参数,我们应该重写哪一个呢?

回到我们原来的设想,是想让程序使用表单进行登录
首先配置一个最简单的,基于表单认证的一个页面配置
这个时候我们需要覆盖configure(HttpSecurity http)方法


 protected void configure(HttpSecurity http) throws Exception {     
        //用表单登录,进行身份认证,所有的请求都需要进行身份认证才可以访问
        http.formLogin()  //表单登录的意思(指定了身份认证的方式)
        //授权
        .and()
        //对请求进行一个授权
        .authorizeRequests()
        .anyRequest().authenticated();//任何请求都需要身份认证
    }


这个时候 启动项目,再次访问请求,会看到一个表单页面(也是spring security提供给我们的)

用户名和密码还是上面一样,user和控制台输出的密码(每次启动密码都会变)
这个时候观察浏览器上的地址变化会和http.httpBasic()这种方式不太一样,我们访问的地址是
//localhost:8080/user/2 在访问的时候会帮我们强制跳转至://localhost:8080/login 页面(登录页面),在认证成功之后会自动帮我们重定向xx/user/2 这个链接。


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


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


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


任何一个Filter成功完成了用户登录以后会在请求上做一个标记(这个用户认证成功了)


最终会到一个FilterSecurityInterceptor过滤器中,是该过滤器链的最后一环。
在这个过滤器中它会决定你当前的请求能不能去访问后面的Controller,依据什么来判断呢?依据我们configure方法中所配置的。

我们现在的配置是:所有的请求都需要经过身份认证才能访问,此时这个Filter会判断当前的请求经过了前面的某一个Filter的身份认证。


针对复杂场景:针对某些请求,只能有VIP用户才能访问,这些规则都会被放在这个FilterSecurityInterceptor里面,这个过滤器会根据这些规则做判断,判断的结果是过还是不过,不过的话,会根据不同的原因抛出不同的异常。比如 如果没有经过身份认证,则抛出一个没身份认证的一个异常;如果只有VIP才能访问这个请求,那么也需要抛出异常,因为权限不够。
在异常抛出去之后,会有一个异常过滤器来拦截ExceptionTranslationFilter,这个异常过滤器就是用来捕获FilterSecurityInterceptor里面所抛出来的异常,它会根据里面抛出来的异常做响应的处理,如果没有没有登录则引导用户去登录等。



在过滤器链中,除了绿色的过滤器(UsernamePasswordAuthenticationFilter/BasicAuthenticationFilter/...)是可以通过配置来禁用或者启用的,对于其他颜色的过滤器都是不能控制的,一定会在过滤器链上,而且位置是不可变的。


2.3通过调试解析spring security登录的一个过程
我们需要在四个类里面打上断点
1.第一个断点(Controller层)


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


3.第三个断点:(ExceptionTranslationFilter层)


4.第四个断点:(UsernamePasswordAuthenticationFilter层)


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


发送请求,因为请求的参数中没有username/password参数,直接就到了最后一个过滤器(FilterSecurityInterceptor)由它来判断是否被拦截。

因为我们配置了所有请求都需要进行身份认证,断点进行下一步时,则会抛出来一个异常(ExceptionTranslationFilter)



捕获到的是未授权异常。这个时候前台页面就会回到了登录页面


在登录页面输入完,正确的账户名和密码之后,断点会来到
UsernamePasswordAuthenticationFilter

它会从request中取到用户名和密码进行校验,校验成功后会继续回到了
(FilterSecurityInterceptor)中的super.beforeInvocation(fi)方法,为什么呢?因为之前的请求是
//localhost:8080/user/2  它是这个资源访问(Controller),在登录成功之后会有个资源的跳转。


在doFilter之后就调到我们自己的Controller中的内容了。

全部完成之后,在页面的前台就可以看到请求的信息了。


2.4自定义登录账户校验
为什么需要自定义呢,因为这个我们的业务场景不仅仅在是个简单账户的校验,需要根据我们自己的业务逻辑来实现相关的功能。
相关功能点:


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


在此接口中只有一个loadUserByUsername方法

根据用户在前台传输过来的用户名来查询用户信息,用户的信息被封装在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接口

我们需要用到它的方法


第一个参数就是用户名,第二个参数就是密码,第三个参数就是我们的角色(做授权)。


此时我们就可以在浏览器上访问我们的请求地址了://localhost:8080/login


账号随便,密码为123456,输入错误的密码则会被抛出来异常


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


处理用户校验逻辑(是否冻结,是否过期等)
常规业务情况需要返回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);


再次启动,登录的用户信息都会是已经失效的用户


处理密码加密解密


处理加密和解密是一个新的接口(PasswordEncoder)。
是org.springframework.security.crypto.password包下面的类。

加密方法是我们自己调用的(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加密后的密码:

当我们使用同一账户进行反复登录时继续观察日志

会发现,两次的加密后的密码是不一致的。
原理
同样的一个密码,随机生成一个盐值,并且在最后生成密码串的时候把随机生成的盐混到这个串里面,每次判断的是可以用随机生成的盐+生成的串去比对,最终来判断密码是否匹配。这样就可以避免同一个密码被反复盗用。

   

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注编程语言JAVA频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 1 不喜欢 | 0
看完这篇文章有何感觉?已经有1人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程