当前位置: 首页 > news >正文

spring-security-oauth2系列:自定义认证服务器

目录

简单示例

自定义clientDetailsService

ClientDetailServiceImpl

程序流程

​​​​​​​Endpoint

​​​​​​​EnableAuthorizationServer

​​​​​​​AuthorizationServerSecurityConfiguration

​​​​​​​ClientDetailsServiceConfiguration

自定义authorizationCodeServices

​​​​​​​RedisAuthenticationCodeServices

​​​​​​​程序流程

​​​​​​​AuthorizationServerEndpointsConfiguration

​​​​​​​authorizationCodeServices

​​​​​​​getEndpointsConfigurer

​​​​​​​AuthorizationEndpoint

​​​​​​​getAuthorizationCodeResponse

​​​​​​​generateCode

自定义tokenServices

​​​​​​​DefaultTokenServices

​​​​​​​getTokenStore

​​​​​​​RedisTokenStore

​​​​​​​程序流程

​​​​​​​postAccessToken

​​​​​​​grant

​​​​​​​getAccessToken

总结


     

       本篇文章我们来研究spring-security框架里如何自定义clientDetailsService, authorizationCodeServices和tokenServices。

       只要使用了spring-security框架,那么,开发人员就免不了要对这些服务进行自定义。

简单示例

       首先,新建一个config包用于存放spring-security通用配置;然后,新建一个WebSecurityConfig类,使其继承WebSecurityConfigurerAdapter。

       然后,给WebSecutiryConfig类中加上@EnableWebSecurity 注解后,这样便会自动被 Spring发现并注册。

       如下所示:

@EnableAuthorizationServer

public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {

... ...

@Autowired

private JedisConnectionFactory jedisConnectionFactory;

@Autowired

private RedisAuthenticationCodeServices redisAuthorizationCodeServices;

@Override

public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

... ...

endpoints.authorizationCodeServices(redisAuthorizationCodeServices);

DefaultTokenServices tokenServices = new DefaultTokenServices();

tokenServices.setSupportRefreshToken(true);

tokenServices.setTokenStore(getTokenStore());

tokenServices.setAccessTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(180)); tokenServices.setRefreshTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(360));

tokenServices.setReuseRefreshToken(false);

endpoints.tokenServices(tokenServices);

}

@Bean

TokenStore getTokenStore() {

return new RedisTokenStore(jedisConnectionFactory);

}

    @Bean

public ClientDetailServiceImpl getClientDetails() {

return new ClientDetailServiceImpl();

}

@Override

public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

// 配置客户端, 用于client认证

clients.withClientDetails(getClientDetails());

}

       在这里:

       通过clients.withClientDetails(getClientDetails())进行自定义服务clientDetailsService;

       通过endpoints.authorizationCodeServices(redisAuthorizationCodeServices)进行自定义服务authorizationCodeServices;

       通过endpoints.tokenServices(tokenServices)进行自定义服务tokenServices。

自定义clientDetailsService

        点击简单示例里的ClientDetailServiceImpl()方法,如下所示:

​​​​​​​ClientDetailServiceImpl

package com.example.author.service.impl;

... ...

public class ClientDetailServiceImpl implements ClientDetailsService {

    @Override

    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {

        SysOauthClient sysOauthClient = selectByClientId(clientId);

        if (sysOauthClient != null) {

            String clientSecret = sysOauthClient.getClientSecret();

            String redirectUrl = sysOauthClient.getRedirectUrl();

            BaseClientDetails client = new BaseClientDetails();

            client.setClientId(clientId);

            client.setClientSecret(clientSecret);

            client.setRegisteredRedirectUri(new HashSet<String>() {{

                add(redirectUrl);

            }});

            client.setAuthorizedGrantTypes(new HashSet<String>() {{

                add("authorization_code");

                add("refresh_token");

                add("client_credentials");

                add("password");

                add("implicit");

            }});

            client.setScope(new HashSet<String>() {{

                add("all");

            }});

            return client;

        } else {

            throw new NoSuchClientException("No client with requested id: " + clientId);

        }

    }

}

       在这里,通过实现框架提供的接口ClientDetailsService并重写loadClientByClientId()方法的方式来实现自定义clientDetailsService的功能。

​​​​​​​程序流程

        通过前面的文章,我们知道/oauth/authorize和/oauth/token两个端点都有依赖服务clientDetailServices,如下所示:

​​​​​​​Endpoint

@Bean

public AuthorizationEndpoint authorizationEndpoint() throws Exception {

AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();

... ...

authorizationEndpoint.setClientDetailsService(clientDetailsService);

... ...

return authorizationEndpoint;

}

@Bean

public TokenEndpoint tokenEndpoint() throws Exception {

TokenEndpoint tokenEndpoint = new TokenEndpoint();

tokenEndpoint.setClientDetailsService(clientDetailsService);

... ...

return tokenEndpoint;

}

        在这里,直接将clientDetailServices赋值给两个端点实例authorizationEndpoint和tokenEndpoint。

       接下来,我们探究一下ClientDetailsServiceConfigurer.withClientDetails()是如何实例化clientDetailsService对象的?

       点击简单示例里的EnableAuthorizationServer,如下所示:

​​​​​​​EnableAuthorizationServer

@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})

public @interface EnableAuthorizationServer {

}

        点击AuthorizationServerSecurityConfiguration类,如下所示:

​​​​​​​AuthorizationServerSecurityConfiguration

@Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class })

public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {

    ... ...

        点击ClientDetailsServiceConfiguration类,如下所示:

​​​​​​​ClientDetailsServiceConfiguration

@Configuration

public class ClientDetailsServiceConfiguration {

private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder());

@Bean

public ClientDetailsServiceConfigurer clientDetailsServiceConfigurer() {

return configurer;

}

@Bean

@Lazy

@Scope(proxyMode=ScopedProxyMode.INTERFACES)

public ClientDetailsService clientDetailsService() throws Exception {

return configurer.and().build();

}

}

      在这里,我们看到了Bean对象clientDetailsService的定义了,而且也知道是通过configurer.and().build()的方式来实例化clientDetailsService对象。具体configurer.and().build()是如何实例化的呢?这里涉及的内容也不少,留着后续再探究。

自定义authorizationCodeServices

        点击简单示例里的RedisAuthenticationCodeServices类,如下所示:

​​​​​​​RedisAuthenticationCodeServices

package com.example.author.service;

@Service

public class RedisAuthenticationCodeServices extends RandomValueAuthorizationCodeServices {

    ... ...

    @Resource

    private RedisTemplate redisTemplate;

    @Override

    protected OAuth2Authentication remove(final String code) {

        OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) redisTemplate.execute(new RedisCallback<OAuth2Authentication>() {

            @Override

            public OAuth2Authentication doInRedis(RedisConnection connection) throws DataAccessException {

                byte[] keyByte = codeKey(code).getBytes();

                byte[] valueByte = connection.get(keyByte);

                if (valueByte != null) {

                    connection.del(keyByte);

                    return (OAuth2Authentication)SerializationUtils.deserialize(valueByte);

                }

                return null;

            }

        });

        return oAuth2Authentication;

    }

    @Override

    protected void store(String code, OAuth2Authentication authentication) {

        try {

            redisTemplate.execute(new RedisCallback<Long>() {

                @Override

                public Long doInRedis(RedisConnection connection) throws DataAccessException {

                    connection.set(codeKey(code).getBytes(), SerializationUtils.serialize(authentication), Expiration.from(5, TimeUnit.MINUTES), RedisStringCommands.SetOption.UPSERT);

                    return 1L;

                }

            });

        } catch (Exception e) {

            log.error("保存authentication code 失败", e);

        }

    }

}

       在这里,通过继承类RandomValueAuthorizationCodeServices并重写remove()和store()方法来实现自定义authorizationCodeServices的功能。

​​​​​​​程序流程

       通过前面的文章,我们知道/oauth/authorize端点有依赖服务authorizationCodeServices,如下所示:

​​​​​​​AuthorizationServerEndpointsConfiguration

@Import(TokenKeyEndpointRegistrar.class)

public class AuthorizationServerEndpointsConfiguration {

  ... ...

@Bean

public AuthorizationEndpoint authorizationEndpoint() throws Exception {

AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();

... ...

authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());

... ...

return authorizationEndpoint;

}

      在这里,将authorizationCodeServices()方法返回的对象赋值给authorizationEndpoint对象的authorizationCodeServices属性。

       点击authorizationCodeServices()方法,如下所示:

​​​​​​​authorizationCodeServices

private AuthorizationCodeServices authorizationCodeServices() throws Exception {

return getEndpointsConfigurer().getAuthorizationCodeServices();

}

        在这里,返回的对象就是我们自定义的authorizationCodeServices对象,点击getAuthorizationCodeServices()方法,如下所示:

​​​​​​​getEndpointsConfigurer

private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();

public AuthorizationServerEndpointsConfigurer getEndpointsConfigurer() {

if (!endpoints.isTokenServicesOverride()) {

try {

endpoints.tokenServices(endpoints.getDefaultAuthorizationServerTokenServices());

}

catch (Exception e) {

throw new BeanCreationException("Cannot create token services", e);

}

}

return endpoints;

}

      在这里,由于我们自定义了tokenServices对象,endpoints.isTokenServicesOverride()是true,所以,getEndpointsConfigurer()方法直接返回AuthorizationServerEndpointsConfigurer对象。

       接下来,我们来探究一下/oauth/authorize端点在接收到HTTP请求后,是如何访问authorizationCodeServices服务的?

​​​​​​​AuthorizationEndpoint

@FrameworkEndpoint

@SessionAttributes({AuthorizationEndpoint.AUTHORIZATION_REQUEST_ATTR_NAME, AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME})

public class AuthorizationEndpoint extends AbstractEndpoint {

... ...

private AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();

... ...

@RequestMapping(value = "/oauth/authorize")

public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters, SessionStatus sessionStatus, Principal principal) {

AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);

Set<String> responseTypes = authorizationRequest.getResponseTypes();

if (!responseTypes.contains("token") && !responseTypes.contains("code")) {

throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);

}

... ...

if (authorizationRequest.isApproved()) {

if (responseTypes.contains("token")) {

return getImplicitGrantResponse(authorizationRequest);

}

if (responseTypes.contains("code")) {

return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,(Authentication) principal));

}

}

        ... ...

}

        点击getAuthorizationCodeResponse()方法,如下所示:

​​​​​​​getAuthorizationCodeResponse

private View getAuthorizationCodeResponse(AuthorizationRequest authorizationRequest, Authentication authUser) {

try {

return new RedirectView(getSuccessfulRedirect(authorizationRequest,

generateCode(authorizationRequest, authUser)), false, true, false);

} catch (OAuth2Exception e) {

return new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, false), false, true, false);

}

}

        点击generateCode()方法,如下所示:

​​​​​​​generateCode

private String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication) throws AuthenticationException {

try {

OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);

OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);

String code = authorizationCodeServices.createAuthorizationCode(combinedAuth);

return code;

} catch (OAuth2Exception e) {

if (authorizationRequest.getState() != null) {

e.addAdditionalInformation("state", authorizationRequest.getState());

}

throw e;

}

}

        在这里,可以看到调用了我们自定义的服务authorizationCodeServices的方法createAuthorizationCode()来生成授权码。

自定义tokenServices

        点击简单示例里的DefaultTokenServices类,如下所示:

​​​​​​​DefaultTokenServices

package org.springframework.security.oauth2.provider.token;

... ...

public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean {

    ... ...

       在这里,通过实例化DefaultTokenServices 对象,并设置tokenStore等属性的方式来实现自定义tokenServices的功能。

       点击简单示例里的getTokenStore()方法,如下所示:

​​​​​​​getTokenStore

@Bean

TokenStore getTokenStore() {

return new RedisTokenStore(jedisConnectionFactory);

}

        点击RedisTokenStore类,如下所示:

​​​​​​​RedisTokenStore

package org.springframework.security.oauth2.provider.token.store.redis;

... ...

public class RedisTokenStore implements TokenStore {

    ... ...

        在这里,类RedisTokenStore也是框架提供的类,可以直接使用。

​​​​​​​程序流程

        通过前面的文章,我们知道/oauth/token端点有依赖服务tokenServices,如下所示:

​​​​​​​postAccessToken

public class TokenEndpoint extends AbstractEndpoint {

    ... ...

@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)

public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws  HttpRequestMethodNotSupportedException {

... ...

OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);

if (token == null) {

throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());

}

return getResponse(token);

}

        在这里,点击getTokenGranter().grant()方法,如下所示:

​​​​​​​grant

public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {

if (!this.grantType.equals(grantType)) {

return null;

}

String clientId = tokenRequest.getClientId();

ClientDetails client = clientDetailsService.loadClientByClientId(clientId);

validateGrantType(grantType, client);

if (logger.isDebugEnabled()) {

logger.debug("Getting access token for: " + clientId);

}

return getAccessToken(client, tokenRequest);

}

        在这里,点击getAccessToken()方法,如下所示:

​​​​​​​getAccessToken

protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {

return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));

}

        在这里,可以看到调用了我们自定义的服务tokenServices服务的createAccessToken()方法来生成TOKEN。

总结

     spring-security框架里自定义clientDetailsService, authorizationCodeServices和tokenServices,同样都是自定义服务,可是差别还是比较大的。

       这个给开发带来比较大的麻烦,显得不太友好。

http://www.lqws.cn/news/544699.html

相关文章:

  • 为什么python处理csv文件将某个值替换成另一个值并另存后,csv文件的大小减小了一半
  • Nginx 与 Apache:有什么区别?哪种服务器最适合您?
  • 【数据挖掘】数据采集和预处理
  • 如何确保京东商品数据采集的准确性和及时性?
  • 【lua】Linux上安装lua和luarocks包管理工具
  • 提升AI准确性的关键:精准添加上下文
  • 【NodeJs】【npm】npm安装electron报错
  • Python Async 编程快速入门 | 超简明异步协程指南
  • C++常用算法解析:sort、find、for_each、copy
  • 阶段二开始-第一章—8天Python从入门到精通【itheima】-116节(封装)
  • cuda编程笔记(5)--原子操作
  • UI前端与数字孪生结合案例分享:智慧零售的可视化解决方案
  • 北京燃气集团管道腐蚀智能预测实践:LSTM算法驱动能源设施安全升级
  • VSCode中创建和生成动态库项目
  • 智能呼叫系统五大核心模式解析
  • 使用mitmdump实现高效实时抓包处理:从原理到实践
  • 技术博客:如何用针孔相机模型理解图像
  • 基于Redis分布式的限流
  • 一款专业的顽固软件卸载工具
  • ubuntu下利用Qt添加相机设备并运行arm程序
  • GO 语言学习 之 变量和常量
  • 神经形态计算与人工智能的融合:从生物启发到智能跃迁的IT新纪元
  • 本地部署Dify+Ragflow及使用(一)
  • PHP语法基础篇(六):数组
  • 通达信 稳定盈利多维度趋势分析系统
  • 鸿蒙OS开发IoT控制应用:从入门到实践
  • 概述-2-MySQL安装及启动-1-Dcoker安装MySQL
  • vue将页面导出pdf,vue导出pdf ,使用html2canvas和jspdf组件
  • Jmeter并发测试和持续性压测
  • 手机屏亮点缺陷修复及相关液晶线路激光修复原理