关于spring-security-oauth2:Spring-Security-Oauth2协议扩展授权模式之通过自定义字段刷新accesstoken

1、Spring Security如何优雅的减少OAuth2协定受权模式?以后教程是对于如何通过自定义字段刷新access_token的,要扩大受权模式请参考这位博主的教程:https://www.cnblogs.com/zlt20... 2、为什么要自定义刷新access_token的关键字段Oauth2协定默认的刷新access_token流程就是仅通过惟一username进行刷新access_token的。当咱们模拟password受权模式扩大出比方手机号/邮箱+明码登录的受权模式且前端不应用username字段或username字段容许反复时,此时咱们就不能通过username字段进行刷新access_token,须要在access_token中携带一个惟一的字段比方userId或mobile提供给受权服务器刷新token应用。因为刷新token与生成token的流程仅有小局部不同。对于Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)剖析,请移步:https://blog.csdn.net/bluuuse... 3、实现过程3.1 整个拷贝UserDetailsByNameServiceWrapper这个类的内容做如下扩大,次要批改loadUserDetails()办法。 package com.nowenti.auth.security.service;import cn.hutool.core.convert.Convert;import com.nowenti.auth.security.exception.UserIdNotFoundException;import org.springframework.security.core.Authentication;import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.util.Assert;/** * @Description: 自定义扩大的受权模式专用ServiceWrapper * 通过用户id加载用户details -> 用于通过user_id刷新token * 自定义以后ServiceWrapper的目标是让刷新token时能够抉择通过用户名或用户id去加载UserDetails * @version 1.0 * @author owen * @email 975706304@qq.com * @date 2021/8/16 23:04 */public class UserDetailsByNameOrIdServiceWrapper<T extends Authentication> implements AuthenticationUserDetailsService<T> { // 结构器注入 private ICustomUserDetailsService userDetailsService; public UserDetailsByNameOrIdServiceWrapper() { } public UserDetailsByNameOrIdServiceWrapper(ICustomUserDetailsService userDetailsService) { Assert.notNull(userDetailsService, "userDetailsService cannot be null."); this.userDetailsService = userDetailsService; } /** * 加载用户详情对象 * authentication.getName()的值有两种状况,须要手动辨别 * 1、user_name * 2、user_id * 3、任意自定义字段 * @param authentication * @return * @throws UserIdNotFoundException */ @Override public UserDetails loadUserDetails(T authentication) { // 从PreAuthenticatedAuthenticationToken获取Principle // Principle是 -> UsernamePasswordAuthenticationToken对象 // UsernamePasswordAuthenticationToken对象的Principle就是nameOrId String usernameOrUserId = authentication.getName(); try { // 能正确转换成Long型用户id -> 会员用户 Long userId = Convert.toLong(usernameOrUserId); return this.userDetailsService.loadMemberUserById(userId); } catch (Exception e) { // 转换异样,usernameOrUserId为用户名 -> 零碎用户 return this.userDetailsService.loadUserByUsername(usernameOrUserId); } } public void setUserDetailsService(ICustomUserDetailsService aUserDetailsService) { this.userDetailsService = aUserDetailsService; }}3.2 整个拷贝DefaultUserAuthenticationConverter这个类的内容做如下扩大,次要批改extractAuthentication()办法 ...

September 10, 2021 · 3 min · jiezi

扩展资源服务器解决oauth2 性能瓶颈

用户携带token 请求资源服务器资源服务器拦截器 携带token 去认证服务器 调用tokenstore 对token 合法性校验资源服务器拿到token,默认只会含有用户名信息通过用户名调用userdetailsservice.loadbyusername 查询用户全部信息详细性能瓶颈分析,请参考上篇文章《扩展jwt解决oauth2 性能瓶颈》 本文是针对传统使用UUID token 的情况进行扩展,提高系统的吞吐率,解决性能瓶颈的问题默认check-token 解析逻辑RemoteTokenServices 入口@Overridepublic OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException { MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>(); formData.add(tokenName, accessToken); HttpHeaders headers = new HttpHeaders(); headers.set(“Authorization”, getAuthorizationHeader(clientId, clientSecret)); // 调用认证服务器的check-token 接口检查token Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers); return tokenConverter.extractAuthentication(map);}解析认证服务器返回的信息DefaultAccessTokenConverterpublic OAuth2Authentication extractAuthentication(Map<String, ?> map) { Map<String, String> parameters = new HashMap<String, String>(); Set<String> scope = extractScope(map); // 主要是 用户的信息的抽取 Authentication user = userTokenConverter.extractAuthentication(map); // 一些oauth2 信息的填充 OAuth2Request request = new OAuth2Request(parameters, clientId, authorities, true, scope, resourceIds, null, null, null); return new OAuth2Authentication(request, user); }组装当前用户信息DefaultUserAuthenticationConverterpublic Authentication extractAuthentication(Map<String, ?> map) { if (map.containsKey(USERNAME)) { Object principal = map.get(USERNAME); Collection<? extends GrantedAuthority> authorities = getAuthorities(map); if (userDetailsService != null) { UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME)); authorities = user.getAuthorities(); principal = user; } return new UsernamePasswordAuthenticationToken(principal, “N/A”, authorities); } return null;}问题分析认证服务器check-token 返回的全部信息资源服务器在根据返回信息组装用户信息的时候,只是用了username如果设置了 userDetailsService 的实现则去调用 loadUserByUsername 再去查询一次用户信息造成问题现象如果设置了userDetailsService 即可在spring security 上下文获取用户的全部信息,不设置则只能得到用户名。增加了一次查询逻辑,对性能产生不必要的影响解决问题扩展UserAuthenticationConverter 的解析过程,把认证服务器返回的信息全部组装到spring security的上下文对象中/** * @author lengleng * @date 2019-03-07 * <p> * 根据checktoken 的结果转化用户信息 */public class PigxUserAuthenticationConverter implements UserAuthenticationConverter { private static final String N_A = “N/A”; // map 是check-token 返回的全部信息 @Override public Authentication extractAuthentication(Map<String, ?> map) { if (map.containsKey(USERNAME)) { Collection<? extends GrantedAuthority> authorities = getAuthorities(map); String username = (String) map.get(USERNAME); Integer id = (Integer) map.get(SecurityConstants.DETAILS_USER_ID); Integer deptId = (Integer) map.get(SecurityConstants.DETAILS_DEPT_ID); Integer tenantId = (Integer) map.get(SecurityConstants.DETAILS_TENANT_ID); PigxUser user = new PigxUser(id, deptId, tenantId, username, N_A, true , true, true, true, authorities); return new UsernamePasswordAuthenticationToken(user, N_A, authorities); } return null; }}给remoteTokenServices 注入这个实现public class PigxResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) { DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter(); UserAuthenticationConverter userTokenConverter = new PigxUserAuthenticationConverter(); accessTokenConverter.setUserTokenConverter(userTokenConverter); remoteTokenServices.setRestTemplate(lbRestTemplate); remoteTokenServices.setAccessTokenConverter(accessTokenConverter); resources. .tokenServices(remoteTokenServices); }}完成扩展,再来看文章开头的流程图就变成了如下关注我个人项目 基于Spring Cloud、OAuth2.0开发基于Vue前后分离的开发平台QQ: 2270033969 一起来聊聊你们是咋用 spring cloud 的吧。 ...

March 20, 2019 · 2 min · jiezi

Spring Cloud OAuth2 资源服务器CheckToken 源码解析

CheckToken的目的 当用户携带token 请求资源服务器的资源时, OAuth2AuthenticationProcessingFilter 拦截token,进行token 和userdetails 过程,把无状态的token 转化成用户信息。 ## 详解OAuth2AuthenticationManager.authenticate(),filter执行判断的入口当用户携带token 去请求微服务模块,被资源服务器拦截调用RemoteTokenServices.loadAuthentication ,执行所谓的check-token过程。源码如下 CheckToken 处理逻辑很简单,就是调用redisTokenStore 查询token的合法性,及其返回用户的部分信息 (username )继续看 返回给 RemoteTokenServices.loadAuthentication 最后一句tokenConverter.extractAuthentication 解析组装服务端返回的信息最重要的 userTokenConverter.extractAuthentication(map);最重要的一步,是否判断是否有userDetailsService实现,如果有 的话去查根据 返回的username 查询一次全部的用户信息,没有实现直接返回username,这也是很多时候问的为什么只能查询到username 也就是 EnablePigxResourceServer.details true 和false 的区别。那根据的你问题,继续看 UerDetailsServiceImpl.loadUserByUsername 根据用户名去换取用户全部信息。关于pig基于Spring Cloud、oAuth2.0开发基于Vue前后分离的开发平台,支持账号、短信、SSO等多种登录,提供配套视频开发教程。 https://gitee.com/log4j/pig

January 25, 2019 · 1 min · jiezi

spring OAuth快速入门

如何阅读前几节简单介绍了OAuth,以及spring secuirty OAuth的一些基本构成。文中有一些关联知识点的引用,如果要深入研究,可以点进去详细了解一下,这样可以加深立即。如果你只想快速浏览OAuth server的搭建可以跳过前面几节,直接看下面的代码实例。什么是OAuth2OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。Oauth2 授权方式authorization_code(授权码类型,使用三方qq登录使用该方式)implicit(隐式授权类型)password(资源所有者即用户密码类型,典型应用是微服务里使用uaa服务器进行登录认证)client_credentials(客户端凭据【客户端ID以及Key】类型,典型应用是微服务内部系统与系统之间调用客服端授权)refresh_token(通过以上授权获得的刷新令牌来获取新的令牌)。关于OAuth不同授权的具体流程可以参考[理解OAuth 2.0-阮一峰](http://www.ruanyifeng.com/blo…。spring Security OAuth2spring security OAuth 是对OAuth2协议的一个实现。是在spring security的基础上发展而来,之前是spring security的一个子项目,现在已经独立出来。详细见官网.spring OAuth使用类似spring secuirty的机制来实现OAuth2 .根据OAuth协议规定,一个完整的授权服务器应当包含:授权服务器和资源服务器。授权服务器负责认证和授权,资源服务器负责根据用户提供的授权凭证(比如给以token)。这里有一篇官方文档介绍了spring OAuth的使用.[OAuth 2 Developers Guide](http://projects.spring.io/spr…授权服务一个授权服务大致几个模块:client管理、授权接口、用户认证、令牌管理。client管理主要用来管理和区分不同的client,我们可以通过配置认证client链接是否合法,能为该client提供哪些授权服务,个性化定制client允许的行为。在spring secruity OAuth2中,可以对client进行如下属性配置:clientId:(必须的)用来标识客户的Id。secret:(需要值得信任的客户端)客户端安全码,如果有的话。scope:用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。authorizedGrantTypes:此客户端可以使用的授权类型,默认为空。authorities:此客户端可以使用的权限(基于Spring Security authorities)。授权接口是授权服务对外提供的http入口。在spring中授权端口如下:spring security OAuth授端口/oauth/authorize:授权端点。对应AuthorizationEndpoint类/oauth/token:令牌端点。对应TokenEndpoint类/oauth/confirm_access:用户确认授权提交端点。对应WhitelabelApprovalEndpoint类/oauth/error:授权服务错误信息端点。对应WhitelabelApprovalEndpoint类/oauth/check_token:用于资源服务访问的令牌解析端点。对应CheckTokenEndpoint类/oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。对应TokenKeyEndpoint类授权是使用 AuthorizationEndpoint 这个端点来进行控制的,你能够使用 AuthorizationServerEndpointsConfigurer 这个对象的实例来进行配置 ,如果你不进行设置的话,默认是除了资源所有者密码(password)授权类型以外,支持其余所有标准授权类型的(RFC6749),我们来看一下这个配置对象有哪些属性可以设置吧,如下列表:authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,请设置这个属性注入一个 AuthenticationManager 对象。userDetailsService:如果啊,你设置了这个属性的话,那说明你有一个自己的 UserDetailsService 接口的实现,或者你可以把这个东西设置到全局域上面去(例如 GlobalAuthenticationManagerConfigurer 这个配置对象),当你设置了这个之后,那么 “refresh_token” 即刷新令牌授权类型模式的流程中就会包含一个检查,用来确保这个账号是否仍然有效,假如说你禁用了这个账户的话。authorizationCodeServices:这个属性是用来设置授权码服务的(即 AuthorizationCodeServices 的实例对象),主要用于 “authorization_code” 授权码类型模式。implicitGrantService:这个属性用于设置隐式授权模式,用来管理隐式授权模式的状态。tokenGranter:这个属性就很牛B了,当你设置了这个东西(即 TokenGranter 接口实现),那么授权将会交由你来完全掌控,并且会忽略掉上面的这几个属性,这个属性一般是用作拓展用途的,即标准的四种授权模式已经满足不了你的需求的时候,才会考虑使用这个用户认证,例如在使用password授权模式时,需要在获取令牌之前先校验用户提供的凭证是否合法,合法的凭证是用户获取授权令牌的前提,spring secuiurty OAuth使用了spring security的认证服务,在令牌获取端口AuthenticationManager进行授权,这个会在后面的授权端口中提到。令牌管理服务负责令牌是生成、校验等操作。令牌主要有两种解决方案:使用随机算法生成唯一标示与用户授权关联,然后保存起来供校验时查询,为了方便资源服务方便验证令牌,这种方案常常是授权服务和资源服务共存的,如果不共存,那么资源服务的tokenStore服务于授权服务tokenStore要做到数据互通,spring 的解决方案是提供/oauth/check_token接口来完成。第二种是授权服务器使用某种算法生成字符串,资源服务器使用约定好的算法对令牌进行解析校验,以验证 他的合法性。在spring security OAuth2中提供了InMemoryTokenStore、JdbcTokenStore、JwtTokenStore。前面两者需要将令牌存在起来,最后一个JwtTokenStore是jwt令牌TokenStore的实现,他不存储令牌,只根据一定的规则和秘钥验证令牌的合法性。jwt令牌分为三段:头部信息、playload、签名。每段使用base64编码,每段之间使用".“连接。资源服务一个资源服务(可以和授权服务在同一个应用中,当然也可以分离开成为两个不同的应用程序)提供一些受token令牌保护的资源,Spring OAuth提供者是通过Spring Security authentication filter来验证过滤器来实现的保护(OAuth2AuthenticationProcessingFilter),你可以通过 @EnableResourceServer 注解到一个 @Configuration 配置类上来标记应用是一个资源服务器,你可以通过配置 ResourceServerConfigurer 配置对象来进行资源服务器的一些自定义配置(可以选择继承自 ResourceServerConfigurerAdapter 然后覆写其中的方法,参数就是这个对象的实例),下面是一些可以配置的属性:tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证。其他的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌,也就说,你可以自定义提如何在请求中提取令牌。请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是受保护资源服务的全部路径。受保护资源的访问规则,默认的规则是简单的身份验证(plain authenticated)。其他的自定义权限保护规则通过 HttpSecurity 来进行配置。@EnableResourceServer 注解自动增加了一个类型为 OAuth2AuthenticationProcessingFilter 的过滤器链。ResourceServerTokenServices 是组成授权服务的另一半,如果你的授权服务和资源服务在同一个应用程序上的话,你可以使用 DefaultTokenServices ,这样的话,你就不用考虑关于实现所有必要的接口的一致性问题,这通常是很困难的。如果你的资源服务器是分离开的,那么你就必须要确保能够有匹配授权服务提供的 ResourceServerTokenServices,它知道如何对令牌进行解码。在授权服务器上,你通常可以使用 DefaultTokenServices 并且选择一些主要的表达式通过 TokenStore(后端存储或者本地编码)。RemoteTokenServices 可以作为一个替代,它将允许资源服务器通过HTTP请求来解码令牌(也就是授权服务的 /oauth/check_token 端点)。如果你的资源服务没有太大的访问量的话,那么使用RemoteTokenServices 将会很方便(所有受保护的资源请求都将请求一次授权服务用以检验token值),或者你可以通过缓存来保存每一个token验证的结果。spring Security OAuth 配置示例关于spring OAuth的更多配置细节可以参考官方文档:OAuth2 Autoconfig.spring Secuirty OAuth2配置比较简单,关键还是要理解OAuth2协议,以及他的使用场景。添加依赖<dependencies> <!– … other dependency elements … –> <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.1.1.RELEASE</version> </dependency></dependencies>授权服务配置授权服务配置主要需要完成以下几个配置:ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。AuthorizationServerSecurityConfigurer:用来配置令牌端点(Token Endpoint)的安全约束.AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)我们可以继承AuthorizationServerConfigurerAdapter并且覆写其中的三个configure方法来进行配置。下面是简单的配置demo,配置来源于官方文档。@Componentpublic class CustomAuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter { AuthenticationManager authenticationManager; public CustomAuthorizationServerConfigurer( AuthenticationConfiguration authenticationConfiguration ) throws Exception { this.authenticationManager = authenticationConfiguration.getAuthenticationManager(); } @Override public void configure( ClientDetailsServiceConfigurer clients ) throws Exception { //下面配置了一个简单的客服端 clients.inMemory()// 使用内存保存客服端信息,建议编写自己的clientService来配置客服端 .withClient(“client”)//客服端的clientid .authorizedGrantTypes(“password”,“client_credentials”,“refresh_token”)//支持的授权方式 .secret(“secret”)//该客服端访问时的密码 .scopes(“all”);//scope范围 } @Override public void configure( AuthorizationServerEndpointsConfigurer endpoints ) throws Exception { endpoints.authenticationManager(authenticationManager); endpoints.tokenStore(tokenStore()); } @Bean public TokenStore tokenStore() { return new InMemoryTokenStore(); } }以上完成了一个简单的授权服务配置,我们的配置重点放在了client的配置上,由于我们为配置资源拥有者(用户),我们无法使用password授权,应为用户信不存在,我们无论输入用户名和密码都不正确。因此我暂且只能使用,client_credentials,这个仅仅是client授权,不牵扯到用户信息。使用下面命令curl client:secret@localhost:8080/oauth/token -d grant_type=client_credentials一切顺利的话我们可以得到token相关信息。如果要使用其他比如password等用户授权,那么需要在授权服务器配置用户相关信息,并把相关的userDetailService配置到AuthorizationServerEndpointsConfigurer对象上,上面有提到endponint配置相关规则。资源服务配置资源服务的作用前面已经介绍过了,资源服务主要关注两个事情,对token进行鉴权,根据具体权限包含资源。由于token可能是其他服务器生成,因此要做到ResourceServerTokenServices与授权服务的tokenService相匹配。下面是简单的配置. @EnableResourceServer public static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { // 注意这个TokenStore需要和之前配置的一致 private final TokenStore tokenStore; public ResourceServerConfiguration(TokenStore tokenStore) { this.tokenStore = tokenStore; } // 资源服务器安全规则配置 @Override public void configure(HttpSecurity http) throws Exception { http .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)) .and() .csrf() .disable() .headers() .frameOptions() .disable() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/register”).permitAll() .antMatchers("/api/activate").permitAll() .antMatchers("/api/authenticate").permitAll() .antMatchers("/api/account/reset-password/init").permitAll() .antMatchers("/api/account/reset-password/finish").permitAll() .antMatchers("/api/").authenticated() // 配置scope权限访问// .antMatchers("/api/").access("#oauth2.hasScope(‘read’) or (!#oauth2.isOAuth() and hasRole(‘ROLE_USER’))"); } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId(“jhipster-uaa”) .tokenStore(tokenStore); } }上面是资源服务和授权服务在一台机器上时的配置。由于共享tokenStore,因此资源服务在验证token的时候不会出任何问题。@Configuration@EnableResourceServer@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfiguration extends ResourceServerConfigurerAdapter { private final OAuth2Properties oAuth2Properties; public SecurityConfiguration(OAuth2Properties oAuth2Properties) { this.oAuth2Properties = oAuth2Properties; } // 主要配置资源的保护规则 @Override public void configure(HttpSecurity http) throws Exception { http .csrf() .disable() .headers() .frameOptions() .disable() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/").authenticated() .antMatchers("/management/health").permitAll() .antMatchers("/management/info").permitAll() .antMatchers("/management/").hasAuthority(AuthoritiesConstants.ADMIN); } // 配置tokenstore,资源服务器在进行token验证的时候需要, @Bean public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) { return new JwtTokenStore(jwtAccessTokenConverter); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(OAuth2SignatureVerifierClient signatureVerifierClient) { return new OAuth2JwtAccessTokenConverter(oAuth2Properties, signatureVerifierClient); }}上面是采用jwt,资源/授权服务分离的配置,以上配置无法和上面的授权服务配置配合使用。若要配置使用,需要修改授权服务的TokenStore为JwtTokenStore,并且在OAuth2JwtAccessTokenConverter中需访问授权服务器/token/access_key获取RSA公钥,该公钥会在token验证时用到。 ...

December 14, 2018 · 2 min · jiezi