美文网首页后端springbootSpring Security
Spring Boot,Spring Security实现OAu

Spring Boot,Spring Security实现OAu

作者: 不小下 | 来源:发表于2017-03-20 14:38 被阅读3812次

阅读此文,希望是对JWT以及OAuth2有一定了解的童鞋。

JWT认证,提供了对称加密以及非对称的实现。

内容源码点我

涉及到源码中两个服务

spring-boot-oauth-jwt-server

spring-boot-oauth-jwt-resource-server


认证服务端

提供认证、授权服务

实现方式,主要复写AuthorizationServerConfigurerAdapter实现

认证服务1-对称加密方式

对称加密,表示认证服务端和认证客户端的共用一个密钥

实现方式

  • AccessToken转换器-定义token的生成方式,这里使用JWT生成token,对称加密只需要加入key等其他信息(自定义)。
          @Bean
          public JwtAccessTokenConverter accessTokenConverter() {
              JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
              converter.setSigningKey("123");
              return converter;
          }
  • 告诉spring security token的生成方式
        @Override
            public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
                endpoints.tokenStore(tokenStore())
                         .accessTokenConverter(accessTokenConverter())
                         .authenticationManager(authenticationManager);
            }

以上对称加密的JWT方式的认证服务端就OK了,后面有对应的资源服务端的内容。

认证服务2-非对称加密方式(公钥密钥)

服务端生成公钥和密钥,每个客户端使用获取到的公钥到服务器做认证。
因此首先要生成一个证书,导出公钥再后续步骤

实现方式

  • 生成JKS文件

      keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore mytest.jks -storepass mypass
    
jks

具体参数的意思不另说明。

  • 导出公钥

      keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey
    
publicKey
  • 生成公钥文本

      -----BEGIN PUBLIC KEY-----
      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhAF1qpL+8On3rF2M77lR
      +l3WXKpGXIc2SwIXHwQvml/4SG7fJcupYVOkiaXj4f8g1e7qQCU4VJGvC/gGJ7sW
      fn+L+QKVaRhs9HuLsTzHcTVl2h5BeawzZoOi+bzQncLclhoMYXQJJ5fULnadRbKN
      HO7WyvrvYCANhCmdDKsDMDKxHTV9ViCIDpbyvdtjgT1fYLu66xZhubSHPowXXO15
      LGDkROF0onqc8j4V29qy5iSnx8I9UIMEgrRpd6raJftlAeLXFa7BYlE2hf7cL+oG
      hY+q4S8CjHRuiDfebKFC1FJA3v3G9p9K4slrHlovxoVfe6QdduD8repoH07jWULu
      qQIDAQAB
      -----END PUBLIC KEY-----
    

存储为public.txt。把 mytest.jks和public.txt放入resource目录下

  • 这里我们要修改JwtAccessTokenConverter,把证书导入
        @Bean
        public TokenEnhancer accessTokenConverter() {
            final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            KeyStoreKeyFactory keyStoreKeyFactory =
                    new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
            converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
        
            converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
            return converter;
        }

以上,就可以实现非对称加密了

额外信息(这部分信息不关乎加密方式)

  • 自定义生成token携带的信息

有时候需要额外的信息加到token返回中,这部分也可以自定义,此时我们可以自定义一个TokenEnhancer
TokenEnhancer 接口提供一个 enhance(OAuth2AccessToken var1, OAuth2Authentication var2) 方法,用于对token信息的添加,信息来源于 OAuth2Authentication

这里我们加入了用户的授权信息。

            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                final Map<String, Object> additionalInfo = new HashMap<>();
                User user = (User) authentication.getUserAuthentication().getPrincipal();
                additionalInfo.put("username", user.getUsername());
                additionalInfo.put("authorities", user.getAuthorities());
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
                return accessToken;
            }

同样要告诉spring security,我们把这个TokenEnhancer加入到TokenEnhancer链中(链,所以可以好多个)

        // 自定义token生成方式
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customerEnhancer(), accessTokenConverter()));
        endpoints.tokenEnhancer(tokenEnhancerChain);
  • 自定义token信息中添加的信息

JWT中,需要在token中携带额外的信息,这样可以在服务之间共享部分用户信息,spring security默认在JWT的token中加入了user_name,如果我们需要额外的信息,需要自定义这部分内容。

JwtAccessTokenConverter是我们用来生成token的转换器,所以我们需要配置这里面的部分信息来达到我们的目的。
JwtAccessTokenConverter默认使用DefaultAccessTokenConverter来处理token的生成、装换、获取。DefaultAccessTokenConverter中使用UserAuthenticationConverter来对应处理token与userinfo的获取、转换。因此我们需要重写下UserAuthenticationConverter对应的转换方法就可以

        @Override
        public Map<String, ?> convertUserAuthentication(Authentication authentication) {
            LinkedHashMap response = new LinkedHashMap();
            response.put("user_name", authentication.getName());
            response.put("name", ((User) authentication.getPrincipal()).getName());
            response.put("id", ((User) authentication.getPrincipal()).getId());
            response.put("createAt", ((User) authentication.getPrincipal()).getCreateAt());
            if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
                response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
            }
        
            return response;
        }

告诉JwtAccessTokenConverter ,把我们的方式替换默认的方式

        @Bean
        public TokenEnhancer accessTokenConverter() {
            final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            converter.setSigningKey("123");
            converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
            return converter;
        }

资源服务端

实现方式,主要复写ResourceServerConfigurerAdapter实现

资源服务1-对称加密方式

此处配置与认证服务基本一致,不同的是,资源服务器配置是在ResourceServerConfigurerAdapter做配置,其他的看源码吧,大差不差。

资源服务2-非对称加密方式(公钥)

把 public.txt放入resource目录下
修改JwtAccessTokenConverter如下:

        @Bean
        public JwtAccessTokenConverter accessTokenConverter() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            Resource resource = new ClassPathResource("public.txt");
            String publicKey = null;
            try {
                publicKey = inputStream2String(resource.getInputStream());
            } catch (final IOException e) {
                throw new RuntimeException(e);
            }
            converter.setVerifierKey(publicKey);
            converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
            return converter;
        }

然后就可以跑起来了。

效果验证

token获取

code获取:

http://127.0.0.1:8081/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=http://127.0.0.1:8082/login/my

redirect_uri:需要与配置在认证服务器中的一致。
client_id:client_id也是预先在认证服务器中,这里是保存在数据库里的
response_type:写死code

浏览器进入后的页面。


密码验证

输入账号密码,这个也是保存在数据库,默认是保存在内存中。

授权之后就可以获得code

http://127.0.0.1:8082/login/my?code=rTKETX

根据这个code,POST下获取token

http://127.0.0.1:8081/oauth/token?grant_type=authorization_code&code=rTKETX&redirect_uri=http://127.0.0.1:8082/login/my&client_id=clientId&client_secret=secret
token获取请求

grant_type:这里写死authorization_code
code:上面得到的rTKETX
redirect_uri:同上不变
client_id:同上不变
client_secret:对应的密码

结果返回如下:


token获取内容

token验证

http://127.0.0.1:8081/oauth/check_token?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

验证token

用户信息获取

用户信息

资源服务端就不展示了...

相关文章

网友评论

  • onlyme_f302:你好 你的用户信息获取 为啥要在认证服务器上获取呢? 不是应该在资源服务端吗 不然这个就脱离了实际应用吗!
  • ed15c2463997:你好,请问这个源码你真的跑成功过吗?
    ed15c2463997:@不小下 看源码很郁闷 尤其是哪个客户端的配置,后来重新找了套 https://blog.csdn.net/qq_37170583/article/details/80704660
    不小下:@易水难求 必须的……不能让我每一个步骤每一个细节都写出来吧。一开始不熟悉,有问题根据报错再排查好点
  • zw900808:有源码吗
    不小下:@zw900808 文章开头有链接
  • 83c2db221172:你好,不知楼主还在吗?这文档是我发现的很全也很明白的文档。给你个赞,我照着你的弄了一个套,发现我如果用你的1.4.5的springboot包访问资源服务器的时候正常,如果把springboot包换为1.5.9访问资源的时候就会返回错误,不知道怎么解决了
    不小下:@靠谱_492f 需要具体看报错信息
  • 奔跑的黄瓜3号:Whitelabel Error Page
    This application has no explicit mapping for /error, so you are seeing this as a fallback.

    Wed Mar 14 18:04:46 CST 2018
    There was an unexpected error (type=Unauthorized, status=401).
    Authentication Failed: Could not obtain user details from token

    授权拿到code回调的页面出错, 不知道博主有调试过浏览器访问没??
    奔跑的黄瓜3号:@你要去哪里_24fa 感觉
    .exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login/my"))
    这里设置有问题。
    奔跑的黄瓜3号:@你要去哪里_24fa 按我的理解,回调url是直接可以访问的.这里我代码里面过滤掉了 .antMatchers("/user_login_entry_point").permitAll() 写了入口方法 ------ @GetMapping(value = "/user_login_entry_point")
    public Object loginByGet(
    @RequestParam(value = "code", required = true) String code,
    @RequestParam(value = "state", required = true) String state) {
    Map<String, String> map = new HashMap<String, String>();
    map.put("code", code);
    map.put("state", state);
    return JSON.toJSON(map);
    }

    我是想回调地址为 http://127.0.0.1:9801/app1/user_login_entry_point?code=77777&state=88888这种,但授权后老是自动重定向到 http://127.0.0.1:9801/app1/login/my?code=emZPhh&state=k5nVnJ 去了 就错误了,

    Whitelabel Error Page
    This application has no explicit mapping for /error, so you are seeing this as a fallback.

    Thu Mar 15 10:16:24 CST 2018
    There was an unexpected error (type=Unauthorized, status=401).
    Authentication Failed: Could not obtain user details from token

    能否告知一下qq 想详聊 :+1:
    03ddb4e5ef2f:回调那个应该是需要自己实现的,作者没说清楚。你仔细研究就会发现根本没有/login/my接口。也就是说你需要自己实现这个接口,然后在通过code获取到token。流程就是这样的。给的sql里面第一条数据有默认的回调url这个不能改。如果是空的话可以随意修改。对照一下微信网页授权就知道了,流程是一样的
  • 03ddb4e5ef2f:我想问下spring-boot-oauth-jwt-resource-server 中的 Controller 接口 不用access_token也能访问,是不是我需要在WebMvcConfig添加配置,目前这个文件没有任何配置。还是说我要自己写一个拦截器拦截没有传access_token的请求?
  • 03ddb4e5ef2f:终于跑起来了,看了很多贴了 下载了好几个源码的就是跑不通。你这个算是比较详细的了,如果能再详细点就好了。
    onlyme_f302:大佬 你跑鉴权那个没有 报 Whitelabel Error Page
    This application has no explicit mapping for /error, so you are seeing this as a fallback.

    Thu Mar 15 10:16:24 CST 2018
    There was an unexpected error (type=Unauthorized, status=401).
    Authentication Failed: Could not obtain user details from token
  • 奔跑的黄瓜3号:没太看懂怎么测试的?能否补全一下吗?还有请求地址也不知道怎么生成 还请能补全
  • 6438ba3cdd77:自己知道在写啥么?简直一塌糊涂
    03ddb4e5ef2f:没看你写过技术文章,有本事你也写一个,我到是想看看是何等高手! :-1:
  • c01665d14f48:可以加一些qq吗?我有一些地方不明白。
    不小下:可以私信我

本文标题:Spring Boot,Spring Security实现OAu

本文链接:https://www.haomeiwen.com/subject/gzabnttx.html