• 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){
            ...
        }

    定义许可计算器

  • 相关阅读:
    函数式编程之Functional.js源码解析(一)
    libevent的bufferevent
    Luna
    线程池的实现
    如何排查字节对齐问题引起的程序诡异崩溃
    GCC编译之如何控制共享文件导出符号
    ubuntu禁用独显的问题
    protobuf的一些细节
    GCC编译之新老版本共存
    libevent的evbuffer之减少内存拷贝
  • 原文地址:https://www.cnblogs.com/geniushuangxiao/p/7301146.html
Copyright © 2020-2023  润新知