1 Star 0 Fork 13

Aocp / SpringCloud-OAuth2-SpringSecurity-Frame

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

2019.07.02

环境:SpringBoot 2.1.0.RELEASE jdk1.8 SpringCloud Greenwich.SR1 consul服务发现与注册 redis

目前完成:

 1、请求前客户端信息完整性校验 
 2、自定义异常返回
 3、无权处理器  
 4、token失效处理器
 5、自定义token返回信息
 6、根据请求URI拦截权限判断
 7、登录登出自定义
 8、用户名密码登录以及手机号码登录
 9、 swagger 集成 OAuth2 
 10、Feign 权限处理

最近在使用SpringCloud 开发权限管理项目,于是设计了一个SpringSecurity OAuth2的统一安全认证。 1、环境 SpringBoot 2.1.0.RELEASE jdk1.8 SpringCloud Greenwich.SR1 consul服务发现与注册
2、项目划分

    (1)、fp-commons  jar公用
    (2)、fp-gateway 网关
    (3)、fp-resource-manager 用户信息管理资源服务器
    (4)、fp-authorization-server OAuth2认证服务器
    (5)、fp-log 日志

3、 项目搭建
(1)网关搭建 frame-gateway

    pom.xml:
        引入依赖文件;在启动器上添加 注册监听@EnableDiscoveryClient 
        并注入 DiscoveryClientRouteDefinitionLocator 
    application.xml: 
        spring-main-allow-bean-definition-overriding: true (相同名称注入允许覆盖)
        spring.application.name:SpringCloud-consul-gateway   (设置应用名称必须)
        bootstrap.yml 中配置 consul(比application先加载) 
        prefer-ip-address: true (使用ip注册,有些网络会出现用主机名来获取注册的问题)

(2)fp-commons jar公用

    pom.xml:
          引入依赖文件;添加一些公用方法

(3)fp-authorization-server

(git中提交了一个至简版本的OAuth2认证中心,只有token生成,没有资源保护以及4种获取token方式(password、authorization_code、refresh_token、client_credentials))

    只需要配置AuthorizationServerConfigurerAdapter 认证服务器
    以及WebSecurityConfigurerAdapter SpringSecurity配置 两个文件,就能实现token生成。
    如果没有需要保护的资源不用ResourceServerConfigurerAdapter 资源服务器配置

AuthorizationServerConfigurerAdapter 认证适配器有三个主要的方法:

1、AuthorizationServerSecurityConfigurer:配置令牌端点(Token Endpoint)的安全约束

(这里我配置了一个处理请求携带客户端信息是否完整的过滤器)

2、ClientDetailsServiceConfigurer:配置客户端详细服务, 客户端的详情在这里进行初始化

(这里我新建了MyClientDetailsService自定义重写了ClientDetailsService接口的loadClientByClientId方法)

    客户端数据是通过ClientDetailsService接口来管理的,默认有两种方式分别是
    InMemoryClientDetailsServiceBuilder  写入内存
    JdbcClientDetailsServiceBuilder      写入和数据库

3、AuthorizationServerEndpointsConfigurer:配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)

(这里我新建了MyUserDetailsService 自定义重写了UserDetailsService的 loadUserByUsername方法)

    用户数据是通UserDetailsService接口来管理的,默认有两种凡是分别是 
    InMemoryUserDetailsManager
    JdbcUserDetailsManager(有默认的表结构需要在数据库中新建)两种对用户数据的管理实现方式

基于自己的项目,我们可能需要定制化一些自己的功能:

1、请求前客户端信息完整性校验
对于携带数据不完整的请求,可以直接返回给前端,不需要经过后面的验证 新建一个ClientDetailsAuthenticationFilter 过滤器,在安全约束中加载到过滤链中

2、自定义异常返回

    默认异常翻译为这种形式:
    {
        "error": "invalid_grant",
        "error_description": "Bad credentials"
    }
    我们期望的格式: 
    {
        "code":401,
        "msg":"msg"
    }

新建MyOAuth2WebResponseExceptionTranslator实现 WebResponseExceptionTranslator接口

重写ResponseEntity translate(Exception e)方法; 认证发送的异常在这里捕获,认证发生的异常在这里能捕获到,在这里我们可以将我们的异常信息封装成统一的格式返回即可,这里怎么处理因项目而异,这里我直接复制了DefaultWebResponseExceptionTranslator 实现方法

定义自己的OAuth2Exception MyOAuth2Exception

定义异常的MyOAuth2Exception的序列化类 MyOAuth2ExceptionJacksonSerializer

将定义好的异常处理 加入到授权服务器的 AuthorizationServerEndpointsConfigurer配置中

        效果:
        {
            "code": 400,
            "msg": "error=\"invalid_request\", error_description=\"Missing grant type\""
        }    

3、无权处理器

  默认的无权访问返回格式是:  
  {
      "error": "access_denied",
      "error_description": "不允许访问"
  }
  我们期望的格式是:
  {
     "code":401,
     "msg":"msg"
  }

新建一个MyAccessDeniedHandler实现AccessDeniedHandler设置返回信息

加入资源配置 ResourceServerConfigurerAdapter

http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);

4、token失效处理器

  默认的token无效返回信息是:
  {
      "error": "invalid_token",
      "error_description": "Invalid access token: 78df4214-8e10-46ae-a85b-a8f5247370a"
  }
  我们期望的格式是: 
  {
     "code":403,
     "msg":"msg"
  }

新建MyTokenExceptionEntryPoint 实现AuthenticationEntryPoint
在 ResourceServerConfigurerAdapter 中注入

    @Override
     public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
         resources.authenticationEntryPoint(tokenExceptionEntryPoint); // token失效处理器
         resources.resourceId("auth"); // 设置资源id  通过client的 scope 来判断是否具有资源权限
     }       

5、自定义token返回信息

默认的token返回格式:

      {
          "access_token": "1e93bc23-32c8-428f-a126-8206265e17b2",
          "token_type": "bearer",
          "refresh_token": "0f083e06-be1b-411f-98b0-72be8f1da8af",
          "expires_in": 3599,
          "scope": "auth api"
      }     

我们期望自定义,例如加上username等:

    {
          "access_token": "1e93bc23-32c8-428f-a126-8206265e17b2",
          "token_type": "bearer",
          "refresh_token": "0f083e06-be1b-411f-98b0-72be8f1da8af",
          "expires_in": 3599,
          "scope": "auth api",
          "username":"username"
    }            

新建自定义token返回MyTokenEnhancer实现TokenEnhancer接口 在认证服务的defaultTokenServices()中添加MyTokenEnhancer。需要注意的是: 如果已经生成了一次没有自定义的token信息,需要去redis里删除掉该token才能再次测试结果,不然你的结果一直是错误的,因为token还没过期的话,是不会重新生成的。

(4)fp-resource-manager 用户信息管理资源服务器

  pom.xml:OAuth2、redis等依赖
  把认证服务器中的 资源配置相关复制到资源项目,稍作调整:
  增加健康检查通过,
  
  增加redis注入(重点,不然会取不到token对比一直是token失效; 如果自定义过userDetails,记得把自定义实现的文件也复制一份到资源项目,不然redis反序列化会失败。)
  到此可以使用token进行资源保护。

6、根据请求URI拦截权限判断 一般我们通过@PreAuthorize("hasRole('ROLE_USER')") 注解,以及在HttpSecurity配置权限需求等来控制权限 在这里,我们基于请求的URI来控制访问权限,并且可以使用注解来控制权限访问。 首先 自定义一个权限认证MySecurityAccessDecisionManager 继承AccessDecisionManager接口,重写 decide方法, 并且复制默认权限验证AbstractAccessDecisionManager的剩余两个方法(实现注解控制的重点)。 用户具有的权限在认证服务器中已经自定义了。

        @Override
        public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
            String requestUrl = ((FilterInvocation) object).getRequest().getMethod() + ((FilterInvocation) object).getRequest().getRequestURI();
    //        System.out.println("requestUrl>>" + requestUrl);
    
            // 当前用户所具有的权限
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
    //        System.out.println("authorities=" + authorities);
            for (GrantedAuthority grantedAuthority : authorities) {
                if (grantedAuthority.getAuthority().equals(requestUrl)) {
                    return;
                }
                if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
                    return;
                }
    
            }
            throw new AccessDeniedException("无访问权限");
        }

这里通过判断匹配通过或者抛出无权异常来判断有无访问权限。 在资源服务器的资源配置中重写权限判断,加载我们自定义的权限判断

 .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {       // 重写做权限判断
       @Override
       public <O extends FilterSecurityInterceptor> O postProcess(O o) {
              o.setAccessDecisionManager(accessDecisionManager);      // 权限判断
              return o;
       }
   })

6、1 优化6的权限判断 先增加一级获取uri然后判断uri需要什么权限,可以多个并且的权限 等等然后 在经过权限判断器进行拦截判断
新建自定义的url权限判断MyFilterInvocationSecurityMetadataSource 实现FilterInvocationSecurityMetadataSource private AntPathMatcher antPathMatcher = new AntPathMatcher(); // 模糊匹配 如何 auth/** auth/auth

        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            Set<ConfigAttribute> set = new HashSet<>();
    
            String requestUrl = ((FilterInvocation) object).getRequest().getMethod() + ((FilterInvocation) object).getRequest().getRequestURI();
            System.out.println("requestUrl >> " + requestUrl);
    
            // 这里获取对比数据可以从数据库或者内存 redis等等地方获取 目前先写死后面优化
            String url = "GET/auth/**";
            if (antPathMatcher.match(url, requestUrl)) {
                SecurityConfig securityConfig = new SecurityConfig("ROLE_ADMIN");
                set.add(securityConfig);
            }
            if (ObjectUtils.isEmpty(set)) {
                return SecurityConfig.createList("ROLE_LOGIN");
            }
            return set;
        }

修改原来的MySecurityAccessDecisionManager,从获取到url 改成获取到需要什么权限。其他判断不变 把MyFilterInvocationSecurityMetadataSource 注册到重写方法

           .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {       // 重写做权限判断
                   @Override
                   public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                       o.setSecurityMetadataSource(filterInvocationSecurityMetadataSource); // 请求需要权限
                       o.setAccessDecisionManager(accessDecisionManager);      // 权限判断
                   return o;
                }
           })

7、登录登出自定义

登出自定义:登出相当于使token失效,我们只需要携带access_token 请求 ConsumerTokenServices(默认自带的注销接口)即可,请求后旧的token即不可用。

        @DeleteMapping("/logout")
        public ResponseVo logout(String accessToken) {
            if (consumerTokenServices.revokeToken(accessToken)) {
                return new ResponseVo(200, "登出成功");
            } else {
                return new ResponseVo(500, "登出失败");
            }
        }

登录自定义:默认的token请求地址是“oauth/token”,我们可以在认证服务配置的 AuthorizationServerEndpointsConfigurer配置中自定义的请求地址。 我们还可以封装一层自己的请求,然后在请求token之前做一些自己的处理。我这里使用了获取需要请求token的信息,然后在java端使用RestTemplate来调用生成token地址 的方式来获取token,我可以在调用之前做一些其他处理,如验证码校验等等。 具体看TokenController文件。

7、1登录优化-增加验证码

登录设置验证码,验证码有效期为1分钟,登录成功后或者到达最大时间后验证码即失效。验证码以用户名_code 的关键字保存在redis中,并设置失效时间 用户名+验证码匹配通过后才进行下一步的token生成,生成token后,即删除验证码。

1.生成带用户名的code 存入redis 设置过期时间
2.生成token前对比验证码。

8、其他方式实现用户名密码登录以及手机号码登录 手机验证码登录 新建MyAuthenticationToken 自定义AbstractAuthenticationToken

新建 MyPhoneAuthenticationToken(手机验证码登录用) 继承 MyAuthenticationToken

新建MyAbstractUserDetailsAuthenticationProvider 抽象类 实现AuthenticationProvider

新建 MyPhoneAuthenticationProvider(手机验证码登录 )继承 MyAbstractUserDetailsAuthenticationProvider ,在这里使用MyPhoneAuthenticationToken,注入参数

这里负责手机验证码的校验。在这里设置各种错误信息 (在MyLoginAuthFailureHandler 登录失败处理器中处理异常返回信息)

修改MyUserDetailsService,改为抽象类,使用模板方法模式: protected abstract AuthUser getUser(String var1); 来获取不同数据来源

新建MyUsernameUserDetailsService继承 MyUsernameUserDetailsService 该方法为原来的登录提供数据

新建MyPhoneUserDetailsService 继承MyUsernameUserDetailsService 该方法为新的手机验证码登录提供数据。

新建MyLoginAuthSuccessHandler 登录成功处理器,该方法用于验证client信息 并返回token信息。

新建MyPhoneLoginAuthenticationFilter 手机验证码登录过滤器,拦截登录的url,进行数据注入到MyPhoneAuthenticationToken。

修改 MySecurityOAuth2Config ,因为修改了 MyUserDetailsService 接口,无法为原来的登录方式提供数据,所以改为MyUsernameUserDetailsService来提供数据

重点 修改MySecurityConfig 配置,装配 两个数据接口,装配 登录成功处理器 装配配置,以及把MyPhoneLoginAuthenticationFilter加入到过滤链。

手机验证码模式登录这里完成,用户名密码登录可以参照该流程自行完成

8、1 优化自定义异常返回格式, 增加登录失败处理器,返回失败异常

9、 swagger 集成 OAuth2

   pom.xml增加  swagger依赖 
   application中增加swagger的配置
   增加配置文件 SwaggerConfig  
   有两种模式  注解掉的是提交增加一个Authorization 提交token框的方式
   方式二:增加token登录框采用登录的方式进行token校验。 
   Swagger 配置登录相关  application配置 登录url
   MySecurityResourceServerConfig 放行 
  
   测试地址:http://192.168.3.14:8001/manager/swagger-ui.html#/

10、Feign 权限处理

    新建一个fp-resource-feign服务
    pom.xml增加Feign的依赖
    Feign 有一个RequestInterceptor接口,对该接口做一些简单的实现,FeignConfig,对请求增加token信息实现授权调用服务接口。

空文件

简介

基于SpringCloud-OAuth2-SpringSecurity-Redis 的一些自定义操作 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/aochenginfo/SpringCloud-OAuth2-SpringSecurity-Frame.git
git@gitee.com:aochenginfo/SpringCloud-OAuth2-SpringSecurity-Frame.git
aochenginfo
SpringCloud-OAuth2-SpringSecurity-Frame
SpringCloud-OAuth2-SpringSecurity-Frame
master

搜索帮助