• spring boot security 实践


    一、大背景

      最近做的自动化测试平台需要进行重构,将原有的系统拆分成几个独立的子系统,我负责用户系统的开发,同时需要兼容老系统,我的头希望我采用spring security来进行权限控制和管理。有以下几个问题需要解决:

      1、如何兼容已有的老的权限体系。

      2、用户系统登录之后,如何将认证信息同步到其它子系统。

    二、调研

      还是按照惯例了解一下spring security到底是什么东西,基本的原理到底是什么。在网上google和看了官方文档之后,发现spring security核心就是一条filter链表。请求过来的时候,会按照一定的顺序逐步通过这些filter,每个filter都验证通过之后,就会到达请求目标,否则就会抛出异常。

      

      拿登陆为例,负责登陆的是UsernamePasswordAuthenticationFilter,请求到达这个过滤器之后,过滤器会把处理丢给认证管理器authenticationManager,认证管理器在把请求丢给Provider,而Provider把内容丢给了DaoAuthenticationProvider,DaoAuthenticationProvider在通过UserDetailService获取响应的用户名和密码。这里就是唯一需要编写代码的地方,也就是实现UserDetailService接口。我们也可以自动以provider

      其他过滤器的内容基本上都类似。贴上各个filter的作用和名称

    过滤器名称

    描述

    o.s.s.web.context.SecurityContextPersistenceFilter

    负责从SecurityContextRepository获取或存储SecurityContext。SecurityContext代表了用户安全和认证过的session。

    o.s.s.web.authentication.logout.LogoutFilter

    监控一个实际为退出功能的URL(默认为/j_spring_security_logout),并且在匹配的时候完成用户的退出功能。

    o.s.s.web.authentication.UsernamePasswordAuthenticationFilter

    监控一个使用用户名和密码基于form认证的URL(默认为/j_spring_security_check),并在URL匹配的情况下尝试认证该用户。

    o.s.s.web.authentication.ui.DefaultLoginPageGeneratingFilter

    监控一个要进行基于forn或OpenID认证的URL(默认为/spring_security_login),并生成展现登录form的HTML

    o.s.s.web.authentication.www.BasicAuthenticationFilter

    监控HTTP 基础认证的头信息并进行处理

    o.s.s.web.savedrequest.

    RequestCacheAwareFilter

    用于用户登录成功后,重新恢复因为登录被打断的请求。

    o.s.s.web.servletapi.

    SecurityContextHolderAwareRequest

    Filter

    用一个扩展了HttpServletRequestWrapper的子类(o.s.s.web. servletapi.SecurityContextHolderAwareRequestWrapper)包装HttpServletRequest。

    它为请求处理器提供了额外的上下文信息。

    o.s.s.web.authentication.

    AnonymousAuthenticationFilter

    如果用户到这一步还没有经过认证,将会为这个请求关联一个认证的token,标识此用户是匿名的。

    o.s.s.web.session.

    SessionManagementFilter

    根据认证的安全实体信息跟踪session,保证所有关联一个安全实体的session都能被跟踪到。

    o.s.s.web.access.

    ExceptionTranslationFilter

    解决在处理一个请求时产生的指定异常

    o.s.s.web.access.intercept.

    FilterSecurityInterceptor

    简化授权和访问控制决定,委托一个AccessDecisionManager完成授权的判断

    三、开撸

      1、导入依赖:我这边用的是springboot,所以使用spring-security 都比较简单,导入对应的依赖即可,如下

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

      2、配置WebSecurityConfig

    import org.springframework.boot.autoconfigure.security.SecurityProperties;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.Order;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
    @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable().authorizeRequests()
                    .antMatchers("/login").permitAll()
                    .antMatchers("/index").hasRole("Admin");
        }
    
    
        /**
         * 权限不通过的处理
         */
        public static class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
            @Override
            public void commence(HttpServletRequest request,
                                 HttpServletResponse response,
                                 AuthenticationException authException) throws IOException {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
                        "Authentication Failed: " + authException.getMessage());
            }
        }
    
    }

      3、自定义登陆:因为需要兼容老的权限体系,所以不想采用spring security 的登陆体系,即采用实现UserDetailService的方式来做,想通过自定义的登陆来实现。找了各方面的资料,发现可以通过获取spring security的上下文,向里面设置认证信息,并将认证信息保存到session中,就可以是实现完全自定义的登陆过程,示例代码如下:

    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import java.util.List;
    
    /**
     * Created by tangrubei on 2018/4/2.
     */
    @Controller
    @CrossOrigin(origins = "*", maxAge = 3600)
    public class MyController {
    
        
    
    
        @GetMapping(value = "login")
        @ResponseBody
        public void Login(HttpServletRequest request,@RequestParam String userName,@RequestParam String password){
            
            if("zhangsan".equals(userName)&&"123456".equals(password)){
    //            设置角色
                List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_"+"Admin");
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password, authorities);
                SecurityContextHolder.getContext().setAuthentication(authRequest);
                HttpSession session = request.getSession();
                session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
            }
        }
    }

      4、自定义的登陆我们实现了,老的系统中,我们的角色和url是在数据库里面动态配置。不能将这些配置直接写死在WebSecurityConfig里或者配置文件里,如果有变更就需要重新进行配置,然后重启应用,这个就太low了,不符合实际场景的应用,我们换另一种方法。在前面的filter列表中我们知道FilterSecurityInterceptor是进行授权和访问控制的,因此我们需要考虑重写AccessDecisionManager或者重写AccessDecisionVoter,我们这边先说第一种

      5、定义AccessDecisionManager的实现类并编写自己的决策逻辑,这里为了方便表示,写了一段伪代码,可以根据后续的需求从数据库中获取或者是别的地方获取

    import org.springframework.security.access.AccessDecisionManager;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.access.ConfigAttribute;
    import org.springframework.security.authentication.InsufficientAuthenticationException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.web.FilterInvocation;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    
    
    /**
     * Created by tangrubei on 2018/3/28.
     */
    @Component
    public class AppAccessDecisionManager
            implements AccessDecisionManager {
    
        private static Map urlMap;
    
        static {
            urlMap = new HashMap();
            urlMap.put("/index", "ROLE_Admin");
            urlMap.put("/login", "permitAll");
    
        }
    
    
        @Override
        public void decide(Authentication authentication, Object object,
                           Collection<ConfigAttribute> configAttributes)
                throws AccessDeniedException, InsufficientAuthenticationException {
    
            if (configAttributes == null) {
                return;
            }
    
            String url = ((FilterInvocation) object).getRequestUrl();
            if (url.indexOf("?") != -1) {
                url = url.substring(0, url.indexOf("?"));
            }
            String needRole = (String) urlMap.get(url);
            if ("permitAll".equals(needRole)) {
                return;
            } else {
                for (GrantedAuthority ga : authentication.getAuthorities()) {
                    if (needRole.trim().equals(ga.getAuthority().trim())) {
                        return;
                    }
                }
            }
            throw new AccessDeniedException("");
    
        }
    
        @Override
        public boolean supports(ConfigAttribute attribute) {
    
            return true;
    
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
    
        }
    
    
    }

    修改WebSecurityConfig

       @Autowired
        private AccessDecisionManager accessDecisionManager;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.csrf().disable().authorizeRequests().antMatchers("/*").authenticated()
                    .accessDecisionManager(accessDecisionManager);
    
    //        http.csrf().disable().authorizeRequests()
    //                .antMatchers("/login").permitAll()
    //                .antMatchers("/index").hasRole("Admin").accessDecisionManager(accessDecisionManager);
        }

    到此,我们就可以自定义登陆,自定义访问策略,同时使用spring security的安全机制。最后一个问题,关于各个子系统认证的问题,我们可以采用session共享的方式来实现,这个比较简单,这里不在复述。  

      

      虽然已经解决了目前所有的问题,但是我们还是可以实践一下实现AccessDecisionVetor这个来实现决策自定义。代码如下:

    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Created by tangrubei on 2018/4/3.
     */
    public class MyVoter implements AccessDecisionVoter {
    
        private static Map urlMap;
    
        static {
            urlMap = new HashMap();
            urlMap.put("/index", "ROLE_Admin");
            urlMap.put("/login", "permitAll");
    
        }
    
        @Override
        public boolean supports(ConfigAttribute configAttribute) {
            return true;
        }
    
        @Override
        public int vote(Authentication authentication, Object o, Collection collection) {
            String url = ((FilterInvocation) o).getRequestUrl();
            if (url.indexOf("?") != -1) {
                url = url.substring(0, url.indexOf("?"));
            }
            String needRole = (String) urlMap.get(url);
            if ("permitAll".equals(needRole)) {
                return ACCESS_GRANTED;
            } else {
                for (GrantedAuthority ga : authentication.getAuthorities()) {
                    if (needRole.trim().equals(ga.getAuthority().trim())) {
                        return ACCESS_GRANTED;
                    }
                }
            }
            return ACCESS_DENIED;
        }
    
        @Override
        public boolean supports(Class aClass) {
            return true;
        }
    }

      写一个对应的配置bean或者在xml里面配置,代码如下:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.access.AccessDecisionManager;
    import org.springframework.security.access.AccessDecisionVoter;
    import org.springframework.security.access.vote.UnanimousBased;
    import spring.boot.security.manager.MyVoter;
    
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * Created by tangrubei on 2018/4/3.
     */
    @Configuration
    public class BeanConfig {
        @Bean(name = "voter")
        public AccessDecisionManager accessDecisionManager(){
            List<AccessDecisionVoter<? extends Object>> decisionVoters
                    = Arrays.asList(new MyVoter() );
            return new UnanimousBased(decisionVoters);
        }
        
    }

      本质上来说与manager没啥区别,只是一个在上层处理掉一个在下一层处理掉,效果基本上以上,附上源码地址:git

     

        

      

  • 相关阅读:
    group_concat函数与find_in_set()函数相结合
    PHP获取指定时间的上个月
    CI框架 数据库批量插入 insert_batch()
    PHP可变长函数方法介绍
    js闭包理解
    Android百度地图开发(一)之初体验
    Activity的四种启动模式和onNewIntent()
    再探Java基础——throw与throws
    Failed to load JavaHL Library解决方法
    Eclipse中添加Android系统jar包
  • 原文地址:https://www.cnblogs.com/rubeitang/p/8709235.html
Copyright © 2020-2023  润新知