• Spring Boot安全设计的配置


      Web应用的安全管理,包括两个方面:一是用户身份认证,即用户登录的设计;另一方面是用户的授权,即一个用户在一个应用系统中能够执行哪些操作的权限管理。我这里使用spring-cloud-security进行安全管理。

      首先是依赖配置

        <parent>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-parent</artifactId>
            <version>Brixton.M5</version>
            <relativePath/>
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <java.version>1.8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
           
        </dependencies>    

      安全策略配置

    @Configuration
    @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
    @EnableConfigurationProperties(SecuritySettings.class)
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
        protected Log log = LogFactory.getLog(getClass());
        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        private SecuritySettings settings;
        @Autowired
        private CustomUserDetailsService customUserDetailsService;
        @Autowired @Qualifier("dataSource")
        private DataSource dataSource;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth)
                throws Exception {
            auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
            //remember me
            auth.eraseCredentials(false);
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {//setting是自定义的配置参数
            http.formLogin().loginPage("/login").permitAll().successHandler(loginSuccessHandler())  //设定一个自定义的的登陆页面URL
                    .and().authorizeRequests()
                    .antMatchers("/images/**", "/checkcode", "/scripts/**", "/styles/**").permitAll()  //完全允许访问的一些URL配置
                    .antMatchers(settings.getPermitall().split(",")).permitAll()
                    .anyRequest().authenticated()
                    .and().csrf().requireCsrfProtectionMatcher(csrfSecurityRequestMatcher())  //跨站请求伪造,这是一个防止跨站请求伪造攻击的策略配置
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
                    .and().logout().logoutSuccessUrl(settings.getLogoutsuccssurl())  //设定登出成功的链接
                    .and().exceptionHandling().accessDeniedPage(settings.getDeniedpage())   //配置拒绝访问的提示链接
                    .and().rememberMe().tokenValiditySeconds(86400).tokenRepository(tokenRepository());  //用来记住用户的登录状态,用户没执行推出下次打开页面不用登陆,时效自己设置
        }
    
        @Bean
        public BCryptPasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public JdbcTokenRepositoryImpl tokenRepository(){
            JdbcTokenRepositoryImpl jtr = new JdbcTokenRepositoryImpl();
            jtr.setDataSource(dataSource);
            return jtr;
        }
    
        @Bean
        public LoginSuccessHandler loginSuccessHandler(){//设置登陆成功处理器
            return new LoginSuccessHandler();
        }
    
        @Bean
        public CustomFilterSecurityInterceptor customFilter() throws Exception{
            CustomFilterSecurityInterceptor customFilter = new CustomFilterSecurityInterceptor();
            customFilter.setSecurityMetadataSource(securityMetadataSource());  
            customFilter.setAccessDecisionManager(accessDecisionManager());
            customFilter.setAuthenticationManager(authenticationManager);
            return customFilter;
        }
    
        @Bean
        public CustomAccessDecisionManager accessDecisionManager() {//
            return new CustomAccessDecisionManager();
        }
    
        @Bean
        public CustomSecurityMetadataSource securityMetadataSource() {
            return new CustomSecurityMetadataSource(settings.getUrlroles());
        }
    
    
        private CsrfSecurityRequestMatcher csrfSecurityRequestMatcher(){ //加入需要排除阻止CSRF攻击的链表链接,链接地址中包含/rest字符串的,对其忽略CSRF保护策略
            CsrfSecurityRequestMatcher csrfSecurityRequestMatcher = new CsrfSecurityRequestMatcher();
            List<String> list = new ArrayList<String>();
            list.add("/rest/");
            csrfSecurityRequestMatcher.setExecludeUrls(list);
            return csrfSecurityRequestMatcher;
        }
    }

      自定义的securityconfig配置,放在application.yml中

    securityconfig:
      logoutsuccssurl: /
      permitall: /rest/**,/bbs**
      deniedpage: /deny
      urlroles: /**/new/** = admin;
                /**/edit/** = admin,editor;
                /**/delete/** = admin

      权限管理规则

    @ConfigurationProperties(prefix="securityconfig")
    public class SecuritySettings {
        private String logoutsuccssurl = "/logout";
        private String permitall = "/api";
        private String deniedpage = "/deny";
        private String urlroles;
    
        public String getLogoutsuccssurl() {//定义推出成功的链接
            return logoutsuccssurl;
        }
    
        public void setLogoutsuccssurl(String logoutsuccssurl) {
            this.logoutsuccssurl = logoutsuccssurl;
        }
    
        public String getPermitall() {//定义允许访问的URL列表
            return permitall;
        }
    
        public void setPermitall(String permitall) {
            this.permitall = permitall;
        }
    
        public String getDeniedpage() {
            return deniedpage;
        }
    
        public void setDeniedpage(String deniedpage) {//定义拒绝访问的信息提示链接
            this.deniedpage = deniedpage;
        }
    
        public String getUrlroles() {
            return urlroles;
        }
    
        public void setUrlroles(String urlroles) {//链接地质与角色权限的配置列表
            this.urlroles = urlroles;
        }
    }

      防攻击策略

    public class CsrfSecurityRequestMatcher implements RequestMatcher {
        protected Log log = LogFactory.getLog(getClass());
        private Pattern allowedMethods = Pattern
                .compile("^(GET|HEAD|TRACE|OPTIONS)$");
        /**
         * 需要排除的url列表
         */
        private List<String> execludeUrls;
    
        @Override
        public boolean matches(HttpServletRequest request) {
            if (execludeUrls != null && execludeUrls.size() > 0) {
                String servletPath = request.getServletPath();
                for (String url : execludeUrls) {
                    if (servletPath.contains(url)) {
                        log.info("++++"+servletPath);
                        return false;
                    }
                }
            }
            return !allowedMethods.matcher(request.getMethod()).matches();
        }
    
        public List<String> getExecludeUrls() {
            return execludeUrls;
        }
    
        public void setExecludeUrls(List<String> execludeUrls) {
            this.execludeUrls = execludeUrls;
        }
    }
    public class CustomAccessDecisionManager implements AccessDecisionManager {
        private static final Logger logger = Logger.getLogger(CustomAccessDecisionManager.class);
    
        @Override
        public void decide(Authentication authentication, Object object,
                           Collection<ConfigAttribute> configAttributes)
                throws AccessDeniedException, InsufficientAuthenticationException {
            if (configAttributes == null) {
                return;
            }
    
            //config urlroles
            Iterator<ConfigAttribute> iterator = configAttributes.iterator();
    
            while (iterator.hasNext()) {
                ConfigAttribute configAttribute = iterator.next();
                //need role
                String needRole = configAttribute.getAttribute();
                //user roles
                for (GrantedAuthority ga : authentication.getAuthorities()) {
                    if (needRole.equals(ga.getAuthority())) {
                        return;
                    }
                }
                logger.info("need role is " + needRole);
            }
            throw new AccessDeniedException("Cannot Access!");
        }
    
        @Override
        public boolean supports(ConfigAttribute configAttribute) {
            return true;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    
    }
    public class CustomFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
        private static final Logger logger = Logger.getLogger(CustomFilterSecurityInterceptor.class);
        private FilterInvocationSecurityMetadataSource securityMetadataSource;
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            FilterInvocation fi = new FilterInvocation(request, response, chain);
            logger.debug("===="+fi.getRequestUrl());
            invoke(fi);
        }
    
        public void invoke(FilterInvocation fi) throws IOException, ServletException {
            InterceptorStatusToken token = super.beforeInvocation(fi);
            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } catch (Exception e) {
                logger.error(e.getMessage());
            } finally {
                super.afterInvocation(token, null);
            }
        }
    
        public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
            return this.securityMetadataSource;
        }
    
        @Override
        public Class<? extends Object> getSecureObjectClass() {
            return FilterInvocation.class;
        }
    
        @Override
        public SecurityMetadataSource obtainSecurityMetadataSource() {
            return this.securityMetadataSource;
        }
    
        public void setSecurityMetadataSource(
                FilterInvocationSecurityMetadataSource smSource) {
            this.securityMetadataSource = smSource;
        }
    
        public void destroy() {
            // TODO Auto-generated method stub
    
        }
    
        public void init(FilterConfig arg0) throws ServletException {
            // TODO Auto-generated method stub
    
        }
    
    }
    public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
        private static final Logger logger = Logger.getLogger(CustomSecurityMetadataSource .class);
    
        private Map<String, Collection<ConfigAttribute>> resourceMap = null;
        private PathMatcher pathMatcher = new AntPathMatcher();
    
        private String urlroles;
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        public CustomSecurityMetadataSource  (String urlroles) {
            super();
            this.urlroles = urlroles;
            resourceMap = loadResourceMatchAuthority();
        }
    
        private Map<String, Collection<ConfigAttribute>> loadResourceMatchAuthority() {
    
            Map<String, Collection<ConfigAttribute>> map = new HashMap<String, Collection<ConfigAttribute>>();
    
            if(urlroles != null && !urlroles.isEmpty()){
                String[] resouces = urlroles.split(";");
                for(String resource : resouces){
                    String[] urls = resource.split("=");
                    String[] roles = urls[1].split(",");
                    Collection<ConfigAttribute> list = new ArrayList<ConfigAttribute>();
                    for(String role : roles){
                        ConfigAttribute config = new SecurityConfig(role.trim());
                        list.add(config);
                    }
                    //key:url, value:roles
                    map.put(urls[0].trim(), list);
                }
            }else{
                logger.error("'securityconfig.urlroles' must be set");
            }
    
            logger.info("Loaded UrlRoles Resources.");
            return map;
    
        }
    
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object)
                throws IllegalArgumentException {
            String url = ((FilterInvocation) object).getRequestUrl();
    
            logger.debug("request url is  " + url);
    
           if(resourceMap == null)
                resourceMap = loadResourceMatchAuthority();
    
            Iterator<String> ite = resourceMap.keySet().iterator();
            while (ite.hasNext()) {
                String resURL = ite.next();
                if (pathMatcher.match(resURL,url)) {
                    return resourceMap.get(resURL);
                }
            }
            return resourceMap.get(url);
        }
    
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }
  • 相关阅读:
    singleTon 模式
    最近的工作经验
    sql server里的快捷键
    Bridge 模式
    [转]在.NET客户端程序中使用多线程
    wse
    关于高频查询界面
    判断字段值已经存在
    获取当前供应商的联系人信息
    获取系统常量
  • 原文地址:https://www.cnblogs.com/shirandedan/p/7266382.html
Copyright © 2020-2023  润新知