SpringSecurity前后端分离下对登录认证的管理

       本案例基于springboot2.0.4,只说对登录验证的管理,不涉及权限的管理。因为学习新东西一下子太多,很容易乱。
       首先添加maven依赖,直接开启springboot自带的spring security即可。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

       接下来配置config,需要继承WebSecurityConfigurerAdapter

@SpringBootConfiguration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private MyAuthenticationFailHandler myAuthenticationFailHandler;

    @Bean
    UserDetailsService customUserService() {
        return new MyCustomUserService();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 使用自定义UserDetailsService
        auth.userDetailsService(customUserService()).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().loginProcessingUrl("/user/login")
                // 自定义的登录验证成功或失败后的去向
                .successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenticationFailHandler)
                // 禁用csrf防御机制(跨域请求伪造),这么做在测试和开发会比较方便。
                .and().csrf().disable();
    }
}

       如果验证数据是从数据库中获取的话还需要添加实现了UserDetailsService接口的类,操作数据使用的是mybatis,代码就不罗列了。

@Component
public class MyCustomUserService implements UserDetailsService {

    @Autowired
    private SysUserMapper sysUserMapper;

    /**
     * 登陆验证时,通过username获取用户的所有权限信息
     * 并返回UserDetails放到spring的全局缓存SecurityContextHolder中,以供授权器使用
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        MyUserDetails myUserDetails = new MyUserDetails();
        myUserDetails.setUsername(username);
        SysUser sysUser = sysUserMapper.selectByKeyword(username);
        myUserDetails.setPassword(sysUser.getPassword());
        return myUserDetails;
    }
}

       上面代码中的MyUserDetails是实现了UserDetails接口,自定义了返回对象。如果不使用MyUserDetails也可以,可以使用默认的User类,只不过验证就复杂点了,为了简化一些不必要的验证属性就自定义了一个UserDetails
下面是User的代码的一些属性。(是不是挺杂乱的对于初学者)

    private static final long serialVersionUID = 500L;
    private static final Log logger = LogFactory.getLog(User.class);
    private String password;
    private final String username;
    private final Set<GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;

       这是自定义的MyUserDetails类,消除了一些不需要的属性对业务的影响。(只保留了3个属性)

@Component
public class MyUserDetails implements UserDetails {

    private String username;

    private String password;

    private Collection<? extends GrantedAuthority> authorities;

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

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

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

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

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

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

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

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

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

       还有两个登陆后去向类,这两个类就随意写了,根据业务需求来写即可。其实spring security本身也已经实现了默认的登录失败和登录成功去向,但是在前后端分离下,后端并不负责页面的跳转或者错误提示等。后端也会返回带有登录信息的Json格式的数据给前端,所以要想满足这些需求就需要实现AuthenticationFailureHandler和AuthenticationSuccessHandler接口。值得注意的是MyAuthenticationSuccessHandler类中的onAuthenticationFailure方法可以打印异常(错误)信息。

@Component
public class MyAuthenticationFailHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        // 允许跨域
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        // 允许自定义请求头token(允许head跨域)
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
        httpServletResponse.getWriter().write(200);
    }
}

@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        // 允许跨域
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        // 允许自定义请求头token(允许head跨域)
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
        httpServletResponse.getWriter().write(e.getMessage());
    }
}

       接下来就是前端的登录代码,该action需要和WebSecurityConfiguration类中loginProcessingUrl(“/user/login”)的值一致。当然了下面的代码也可以用ajax来替代,不一定要用from表单登录。

<form action='/user/login' method='POST'>
    <table>
        <tr>
            <td>User:</td>
            <td><input type='text' name='username' value=''></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><input type='password' name='password'/></td>
        </tr>
    </table>
</form>

       以上就是简单的spring security在前后端分离下的运用例子。不过光是这么写,不明白的人看了还是一头雾水,也不能充分展示spring security的强大。

--------------------------------------

重点再说下WebSecurityConfiguration类
在WebSecurityConfiguration类中需要把自定义的MyAuthenticationSuccessHandler和MyAuthenticationFailHandler去向类注入,并在loginProcessingUrl(“/user/login”)后面激活这两个类的使用

@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private MyAuthenticationFailHandler myAuthenticationFailHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin().loginProcessingUrl("/user/login")
            // 自定义的登录验证成功或失败后的去向
            .successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenticationFailHandler)
            // 禁用csrf防御机制(跨域请求伪造),这么做在测试和开发会比较方便。
            .and().csrf().disable();
}

自定义MyUserDetailsService类也需要在WebSecurityConfiguration类中注入和激活使用

@Bean
UserDetailsService customUserService() {
    return new MyCustomUserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 使用自定义UserDetailsService
    auth.userDetailsService(customUserService()).passwordEncoder(new BCryptPasswordEncoder());
}

       上面的代码有个.passwordEncoder(new BCryptPasswordEncoder())这个意思就是指定密码加密的方式。这里采用的是BCrypt加密方式。换句话说,当spring security获取到你前端传过来的密码时,就自动用BCrypt加密密码,然后拿去和数据库密码匹对。所以在数据库里存的密码也得是用BCrypt加密后的密码。
       如果这么说,是不是也可以指定自定义加密方式呢。是可以的,实现也很简单,只要实现PasswordEncoder接口即可。在把.passwordEncoder(new BCryptPasswordEncoder())更改为.passwordEncoder(new MyPasswordEncoder())即可。

@Component
public class MyPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence charSequence) {
        return SecurityUtil.encrypt(charSequence.toString());
    }

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

接下来说下这个

http.formLogin().loginProcessingUrl("/user/login")
          // 自定义的登录验证成功或失败后的去向
          .successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenticationFailHandler)
          // 禁用csrf防御机制(跨域请求伪造),这么做在测试和开发会比较方便。
          .and().csrf().disable();

       关于这个一般来写的话都是有http.forLogin().loginPage(“/login”)啥的,这个是自定义登录页面,默认为/login。说白了就是你访问spring security保护的请求时候如果发现你没有登录,就会跳转到指定页面,使用默认或者不写的话spring security就会跳转到已经帮你写好了一个默认登录页面了。在前后端分离下,这个就不管了。loginProcessingUrl(“/user/login”)就是登录请求。

光是看上面的似乎有点囫囵吞枣,建议在看另一篇博文,主要分析登录验证的过程和一些源码的浅析。(写的不咋滴,但是认真看完后我觉得应该会有所收获)
https://blog.csdn.net/XlxfyzsFdblj/article/details/82084183

猜你喜欢

转载自blog.csdn.net/XlxfyzsFdblj/article/details/82083443