Protogalaxy

Planet #0

PHSS-Core开发日志#8 Spring Security踩坑指南

在完成了由Hibernate到Spring Data JPA的切换后,我开始了Service层的设计,由于PHSS对安全性以及开发速度的需求,我选择了使用同属Spring Projects系列的Spring Security。

安全是一个非常抽象且不断变化的概念,在如今的Web环境下,一个动态的,不断完善的安全系统显得至关重要。在现代Web应用中,只有建立一个完善的安全层才能尽可能保证各个层级的安全性。

在现在的网络环境中,常见的攻击手段有XSS,SQL注入,CSRF,SSRF,xee,xPath注入等。

我们要做的就是在安全层建立完备的系统来防御这些可以预见的以及其他不可预见的攻击。在系统底层,我们需要处理诸如传输安全与系统识别的问题,最简单的例子就是开启https连接使用X.509认证以防止传输过程中的中间人攻击。然后我们还需要将关键的数据-例如密码与身份证号等各种信息进行加密储存以最大程度的防止系统被攻破后的数据泄露。在Service层,我们需要一套完善的权限管理机制来确保数据只能被拥有相关权限的用户访问到,更加高级的还有方法级的授权验证等。

以以上手段为出发点,我开始进行Spring Security的基本配置。由于PHSS是基于Spring Boot的项目,所以首先是基于boot的Spring Security包的引入,包名为spring-boot-strater-security。

接下来就是进行认证及授权相关的配置。首先是通过java config开启Spring Security支持,示例如下:

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class PhssMainSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/index", "/user/signup", "/user/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/user/login")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/user/logout")
                .logoutSuccessUrl("/index");
    }

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

    @Bean
    @Override
    protected PhssUserDetailsService userDetailsService() {
        return new PhssUserDetailsService();
    }
}

在以上的类中,我们做了以下几件事情:

  1. 使用@EnableWebSecurity开启Spring Security支持。
  2. 使用@EnableGlobalMethodSecurity来开启整个应用的方法级安全保护。
  3. 重载configure方法自定义Filter chain来控制url的访问权限以及指定login与logout处理逻辑。
  4. 通过声明PasswordEncoder bean来指定需要使用的密码加密算法。
  5. 通过重写userDetailsService方法来使用自定义的UserDetailsService

UserDetailsService是Spring Security指定的用户实体操纵接口,其核心方法为loadUserByUserName(),可以从字面意思看出,这个方法是通过用户名来fetch用户实体,Spring Security正是通过这个方法来获取数据库中的用户密码Hash值来与用户上传的密码Hash值进行对比来验证用户是否可以登录。

在自定义UserDetailsService之前,我们还需要进行一些准备工作,即UserDetails对象的编写,在PHSS中,我使用了继承UserDetails的User实体类来实现UserDetails的定制,在UserDetails中,有几个核心接口需要我们一一实现,分别是getUsername(),getPassword(),isEnabled(),isAccountNonExpired(),isAccountNonLocked().isCredentialNonExpired(),getAuthorities(),eraseCredentials(),从字面意思上我们不难看出这些方法的用途,需要重点关注的是getAuthorities()方法,这个方法的用处是获取用户的权限信息,也就是ROLE,下面是PHSS的getAuthorities()实现:

@Override
public Set<PhssGrantedAuthority> getAuthorities() {
    String[] authoritiesString = authorities.split(",");
    Set<PhssGrantedAuthority> authoritiesSet = new HashSet<>();
    for (String anAuthoritiesString : authoritiesString) {
        authoritiesSet.add(new PhssGrantedAuthority(("ROLE_" + anAuthoritiesString)));
    }
    return authoritiesSet;
}

可以看出这个方法通过解析并修饰数据库中的Role信息并返回了一个自定义的GrantedAuthority,需要注意的是在Spring Security中,权限信息必须以ROLE_作为开头。下面是PhssGrantedAuthority的具体实现:

public class PhssGrantedAuthority implements GrantedAuthority {
    private static final String USER_AUTHORITY = "ROLE_USER";
    private static final String ADMIN_AUTHORITY = "ROLE_ADMIN";
    private static final String GUEST_AUTHORITY = "ROLE_GUEST";
    private final String role;

    public PhssGrantedAuthority(String role) {
        if (!(role.contains(ADMIN_AUTHORITY) || role.contains(USER_AUTHORITY) || role.contains(GUEST_AUTHORITY))) {
            throw new IllegalArgumentException("role set illegal");
        }
        this.role = role;
    }

    @Override
    public String getAuthority() {
        return role;
    }

    @Override
    public String toString() {
        return this.role;
    }

    @Override
    public int hashCode() {
        return this.role.hashCode();
    }
}

可以看出这个GrantedAuthority其实是一个简单的权限封装类,需要重写的有get,toString与Hash方法。在PhssGrantedAuthority的构造器中,我加入一个简单的字段验证来保证封装的用户角色是预先指定的USER或者ADMIN。

至此一个简单的Spring Security框架便成功的加入到了PHSS的结构之中。

发表评论