微服务架构开发一 oauth2资源服务器 集成spring security

我是用spring security,oauth2 ,zuul,springcloud做路由,同时进行api接口授权管理。

先贴出pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>zuulCenter</artifactId>
    <version>0.1</version>
    <packaging>jar</packaging>

    <name>zuulCenter</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.neusoft</groupId>
        <artifactId>center</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>com.neusoft</groupId>
            <artifactId>center_common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 路由 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>2.0.1.RELEASE</version>
        </dependency>
        <!-- 安全验证 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.0.6.RELEASE</version>
        </dependency>

</project>
 

 

启动类

package com;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

/** 服务网管
 * @author  wf
 * @Time 2019年1月17日 15:19
 * @version 1.0
 */
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
@MapperScan("com.neusoft.dao")
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

oauth2的资源代码

package com.neusoft.config;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.web.cors.CorsConfiguration;

/**
 * @Description: 配置资源服务器(oauth2)
 * @author: wf
 * @Date: 2018-11-26
 * @Time: 15:08
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true)
public class ResourceServiceConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http .csrf().disable()
        .authorizeRequests()
 .antMatchers("/**")
 .authenticated()
 .antMatchers("actuator/*").permitAll()//定义这两个链接不需要登录可访问

   .and()
 .exceptionHandling()
 .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
               /* .permitAll()*/
                //跨域配置
                .and().cors().configurationSource(request -> {
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            corsConfiguration.addAllowedHeader("*");
            corsConfiguration.addAllowedOrigin(request.getHeader("Origin"));
            corsConfiguration.addAllowedMethod("*");
            corsConfiguration.setAllowCredentials(true);
            corsConfiguration.setMaxAge(3600L);
            return corsConfiguration;
        });
        //自定义过滤器
        //把springsecurity决策管理那套东西通过过滤器加进来
     /*  http.addFilterAt(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);   */
        
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources)
            throws Exception {
        super.configure(resources);
    }

       
}

配置apppliction.yml

spring:
  profiles:
    #包含的配置项
    include:
    - single-datasource
    #      - quartz
    #启用配置项级别
    active: prod
---    
spring:    
    application:
     name: service-zuul
    redis:
    host: localhost
    port: 6379 
server:
  port: 8082

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8080/eureka/
mybatis:
  mapper-locations: classpath*:**/mappers/*.xml
  type-aliases-package: com.neusoft.entity
  configuration:
    map-underscore-to-camel-case: true
    use-generated-keys: true
    use-column-label: true
    cache-enabled: true
    call-setters-on-nulls: true
logging:
  level:
    root: error
    org:
      springframework: info
      
security:
  oauth2:
    authorization:
      check-token-access: http://127.0.0.1:8081/oauth/check_token
    resource:
      user-info-uri: http://127.0.0.1:8081/user/me
      id: oa      
      
      ###路由
zuul:
  routes:
    api-a: #id
      path: /api-a/**  #路径
      serviceId: EURKA-CLIENT1 #子服务id
    api-b:
      path: /api-b/**
      serviceId: EURKA-CLIENT131/

 

最后就是加入springsecurity 决策器的三大件,决策器,资源管理,用户管理(因为认证是通过认证服务器来,固此出去去掉),拦截器

决策器:

import java.util.Collection;
import java.util.Iterator;

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.stereotype.Service;

/**决策器
 * @author 王峰
 * @Time:2019年1月18日 下午4:37:30
 * @version 1.0
 */
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
  // decide 方法是判定是否拥有权限的决策方法,
  //authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
  //object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
  //configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {

        if(null== configAttributes || configAttributes.size() <=0) {
            return;
        }
        ConfigAttribute c;
        String needRole;
        for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
            c = iter.next();
            needRole = c.getAttribute();
            for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
                if(needRole!=null&&needRole.trim().equals(ga.getAuthority())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("无权访问,请联系管理员");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
    
    
}

资源管理器

package com.neusoft.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;

import com.neusoft.center_common.utils.LogUtil;
import com.neusoft.entity.Resouces;
import com.neusoft.entity.Role;
import com.neusoft.servers.ResouceService;
import com.neusoft.servers.RoleService;

/**资源管理
 * @author 王峰
 * @Time:2019年1月18日 下午4:38:36
 * @version 1.0
 */
@Service
public class MyInvocationSecurityMetadataSourceService  implements
        FilterInvocationSecurityMetadataSource {

    @Autowired
    private RoleService roleService;
    @Autowired
    private ResouceService resouceService;
    

    private HashMap<String, Collection<ConfigAttribute>> ma =null;

    /**
     * 加载权限表中所有权限
     */
    public void loadResourceDefine(){
        try {
        
        
        ma = new HashMap<String, Collection<ConfigAttribute>>();
    List<Resouces>resources=    resouceService.queryAll();
        for (Resouces re : resources) {
            Collection<ConfigAttribute> cc = new ArrayList<ConfigAttribute>();
            ConfigAttribute ca = null;
            List<Role> roles = roleService.queryByResource(re.getAuth_res_Id());
            if (roles == null) {

            } else {
                for (Role r : roles) {
                    ca = new SecurityConfig(r.getAuth_role_id());
                    cc.add(ca);
                }
                ma.put(re.getAuth_res_url(), cc);
            }
        }
        } catch (Exception e) {
        LogUtil.doErr("资源管理中查询全部资源", e);
        }

    }

//此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        if(ma ==null) loadResourceDefine();
        //object 中包含用户请求的request 信息
      String url= ((FilterInvocation) object).getRequestUrl();
        AntPathRequestMatcher matcher;
        String resUrl;
        for(Iterator<String> iter = ma.keySet().iterator(); iter.hasNext(); ) {
            resUrl = iter.next();
            if(url.contains(resUrl)) {
                return ma.get(resUrl);
            }
        }
        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

拦截器(保证springsecurity加进来)

package com.neusoft.config;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;

/**有这个过滤器才会走springSecurity
 * 
 * @author 王峰
 * @Time:2019年1月23日 下午5:33:45
 * @version 1.0
 */
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {


    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
        super.setAccessDecisionManager(myAccessDecisionManager);
    }


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }


    public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
//执行下一个拦截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }


    @Override
    public void destroy() {

    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;

    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }
}

 

在做这块的时候,遇到一个问题就是不知道怎么把springsecurity和oauth2集成在一起,本来想是不是springsecurity拦截器要加入到oauth中,这样才能启动拦截,最早的做法是微服务架构开发一 oauth2资源服务器 集成spring security

发现加入红框中的内容,权限拦截会执行两次,后来把他去掉就正常了,我猜想是不是加入了安全jar包之后,springcloud自动把他配置好了。