首页 > 代码库 > 007Spring Security

007Spring Security

01、基于Spring AOP 和 Servlet规范中Filter实现  的安全框架

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.2.3.BUILD-SNAPSHOT</version>
</dependency>

02、在Web请求级别 & 方法调用级别 处理身份认证和授权

03、Spring Security配置

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(WebSecurity webSecurity){
        //配置Spring Security的Filter链
    }
    @Override
    protected void configure(HttpSecurity httpSecurity){
        //配置如何通过拦截器保护
    }
    @protected void configure(AuthenticationManagerBuilder auth){
        //配置user-detail服务
    }
}

04、配置user-detail服务

内存存储

@Override
protected void configure(AuthenticationManagerBuilder auth){
    auth.inMemoryAuthenticatioin()
        .withUser("user").password("password").roles("USER").and()
        .withUser("admin").password("password").roles("USER", "ADMIN");//roles("USER")==authorities("ROLE_USER"),roles会加ROLE_前缀
}
accountExpired(boolean)        定义账号是已否过期
accountLocked(boolean)        是否已锁定
and()        连接配置
authorities(String ...)        给用户授权
credentialsExpired(boolean)    定义凭证是否已过期
disabled(boolean)    定义账号是否被禁用
password(String)    定义用户密码
roles(String ...)    给用户授权

数据库表查询

@Autowired
DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.jdbcAuthentication().dataSource(dataSource)
        .passwordEncoder(new StandardPasswordEncoder("53cr3t"));
}
数据库中须存在如下5张表
users : username, password, enabled
authorities : username, authority
groups : id, group_name
group_members : group_id, username
group_authorities : group_id, authority

05、加密模块

    接口:
        public interface PasswordEncoder {
            String encode(CharSequence rawPassword);
            boolean matches(CharSequence rawPassword, String encodedPassword);
        }
    3个实现:
        BCryptPasswordEncoder---->uses the BCrypt strong hashing function,推荐加密算法
        NoOpPasswordEncoder---->未做任何加密
        StandardPasswordEncoder---->uses SHA-256 hashing with 1024 iterations and a random 8-byte random salt value

06、细粒度安全控制---->将最具体的规则放在上面

重载configure(HttpSecurity)方法
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/spitters/me").authenticated()---->ant风格
        .antMatchers(HttpMethed.POST, "/spittles").authenticated()
        .antMatchers("/spitters/admin").hasAuthority("ROLE_SPITTER")---->有ROLE_SPITTER权限的用户可访问
        .antMatchers("/spitters/user").hasRoles("SPITTER")---->有ROLE_SPITTER权限的用户可访问
        .regexMatcher("/spitters/.*").authenticated()---->正则表达式
        .anyRequest().permitAll();---->其它请求不需要认证
}
authenticated()---->允许认证过的用户访问。否则,Spring Filter将捕获此请求,并重定向到登陆页面
permitAll()---->无条件允许访问
access(String)---->给定SpEL表达式为true,则允许访问
anonymous()---->允许匿名访问
denyAll()---->无条件拒绝所有访问
fullyAuthenticated()---->用户是完整认证(不是通过Remember-me功能认证),就允许访问
hasAnyAuthority(String ...)---->用户具备给定权限中一个,则允许访问
hasAnyRole(String ...)---->用户具备给定角色中一个,则允许访问
hasAuthority(String)---->用户具备给定权限,则允许访问
hasIpAddress(String)---->请求来自给定IP,则允许访问
hasRole(String)---->用户具备给定角色,则允许访问
not()---->对其它访问方法结果取反
rememberMe()---->用户通过Remember-me功能认证,则允许访问

07、access(String SpEL)安全策略

Spring Security扩展了SpEL语言
    authentication        用户认证的对象
    denyAll                结果始终为false
    hasAnyRole(list of roles)    用户被授予任一指定角色,则为true
    hasRole(role)        用户被授予指定角色,则为true
    hasIpAddress(IP Address)    请求来之指定IP,则为true
    isAnonymous()        当前用户为匿名用户,则true
    isAuthenticated()    当前用户已经认证,则true
    isFullyAuthenticated()    完整认证
    isRememberMe()        Remember-me认证
    permitAll            始终true
    principal            用户的principal对象
antMatchers("/spitter/me").access("hasRole(‘ROLE_SPITTER‘) and hasIpAddress(‘192.168.1.2‘)")

08、HTTP & Https

    表单提交,应该用加密通道

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/spitter/me").hasRole("SPITTER")
            .anyRequest().permitAll()
        .and()
        .requiresChannel()
            .antMatchers("/spitter/form").requiresSecure();---->须要https通道。若为http通道请求,将自动重定向到https通道
}

    网页首页,不该用加密通道

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/spitter/me").hasRole("SPITTER")
            .anyRequest().permitAll()
        .and()
        .requiresChannel()
            .antMatchers("/").requiresInSecure();---->须要http通道。
}

08、HTTP Basic Authentication

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin().loginPage("/login")
        .and()
        .httpBasic().realmName("Spittr")---->启用 HTTP Basic 认证
        .and()...
}

09、Remember-me功能

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin().loginPage("/login")
        .and()
        .rememberMe().tokenValiditySeconds(2419200).key("spittrKey");
}

    默认情况下,此功能通过cookie中存储token实现,默认2周有效。此处指定4周有效。
    token包含用户名、密码、过期时间、一个私钥,均MD5哈希。
    默认私钥名"SpringSecured", 将其设置为"spitterKey",使之专用于Spittr应用。
    
    登陆请求添加remember-me复选框
        <input id = "remember_me" name = "remember-me" type = "checkbox"/>

10、logout

    默认logout通过Servlet中LogoutFilter实现,此Filter拦截对/logout的请求,退出后,重定向到"/login?logout"
    
    logout时,所有Remember-me token都会被清除。
    
    更改默认配置

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin().loginPage("/login")
        .and()
        .logout().logoutSuccessUrl("/")---->成功退出后重定向到/
                 .logoutUrl("/signout");---->LoginoutFilter拦截路径
}

11、保护方法安全

配置

@Configuration
@EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)---->将创建一个切点,Spring Security切面会包装带@Secured注解的方法。
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    ...
}

@Secured

@Secured({SROLE_SPITTER", "ROLE_ADMIN"})---->参数为权限数组,调用者必须具备其中一个权限
public void addSpittle(Spittle spittle){
    ...
}

@PreAuthrize---->方法调用前验证权限

@PreAuthrize("hasRole(‘ROLE_SPITTER‘) and #spittle.text.length() <= 140 or hasRole(‘ROLE_PREMIUM‘)")
public void addSpittle(Spittle spittle){
    ...
}

@PostAuthrize---->方法调用后验证权限,主要用于验证返回值

@PostAuthrize("returnObject.spitter.username == principal.username")---->returnObject为返回值对象,principal为当前认证用户主要信息
public Spittle getSpittleById(long id){
    ...
}

@PostFilter---->返回值过滤

@PostAuthrize("hasAnyRole({‘ROLE_SPITTER‘, ‘ROLE_ADMIN‘})")
@PostFilter("hasRole(‘ROLE_ADMIN‘) || filterObject.spitter.username == principal.name")
public List<Spittle> getOffensiveSpittles(){---->若非admin用户,只能看到自己的spitter

}

@PreFilter---->参数过滤

@PreAuthorize("hasAnyRole({‘ROLE_SPITTER‘, ‘ROLE_ADMIN‘})")
@PreFilter("hasRole(‘ROLE_ADMIN‘) || targetObject.spitter.username == principal.name")
public void deleteSpittles(List<Spittle> spittles){
    ...
}

定义许可计算器

配置:
@Configuration
@EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)---->将创建一个切点,Spring Security切面会包装带@Secured注解的方法。
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler(){
        DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(new SpittlePermissionEvaluator());
        return expressionHandler;
    }
}
hasPermission()许可计算器:
    public class SpittlePermissionEvaluator implements PermissionEvaluator {
        private static final GrantedAuthority ADMIN_AUTHORITY = new GrantedAuthorityImpl("ROLE_ADMIN");
        
        public boolean hasPermission(Authentication authentication, Object target, Object permission){
            if(target instanceof Spittle){
                Spittle spittle = (Spittle) target;
                String username = spittle.getSpitter().getUsername();
                if("delete".equals(permission)){
                    return isAdmin(authentication) || username.equals(authentication.getName());
                }
            }
            String exp = String.format("hasPermission not supported for object <%s> and permission <%s>", object, permission);
            throws new UnsupportedOperationException(exp);
        }
        
        public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission){
            throws new UnsupportedOperationException();
        }
        private boolean isAdmin(Authentication authentication){
            return authentication.getAuthorities().contains(ADMIN_AUTHORITY);
        }
    }
使用hasPermission()许可计算器:
    @PreAuthorize("hasAnyRole({‘ROLE_SPITTER‘, ‘ROLE_ADMIN‘})")
    @PreFilter("hasPermission(targetObject, ‘delete‘)")
    public void deleteSpittles(List<Spittle> spittles){
        ...
    }

 

定义许可计算器

 

007Spring Security