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()办法

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//package com.nowenti.auth.security.converter;import java.util.Collection;import java.util.LinkedHashMap;import java.util.Map;import com.nowenti.auth.security.service.ICustomUserDetailsService;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.AuthorityUtils;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;import org.springframework.util.StringUtils;/** * @Description: 自定义的用户认证转换器 * 自定义以后转换器的目标是为了让自定义的受权模式反对通过user_id刷新token * @version 1.0 * @author owen * @email 975706304@qq.com * @date 2021/8/19 10:06 */public class CustomUserAuthenticationConverter implements UserAuthenticationConverter {    private Collection<? extends GrantedAuthority> defaultAuthorities;    private ICustomUserDetailsService userDetailsService;    public CustomUserAuthenticationConverter() {    }    /**     * 结构器注入userDetailsService     * @param userDetailsService     */    public void setUserDetailsService(ICustomUserDetailsService userDetailsService) {        this.userDetailsService = userDetailsService;    }    public void setDefaultAuthorities(String[] defaultAuthorities) {        this.defaultAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.arrayToCommaDelimitedString(defaultAuthorities));    }    /**     * 将UsernamePasswordAuthenticationToken对象转换成一般map     * 该map的内容将被增加进access_token和refresh_token     * 该办法后于extractAuthentication()执行     * 该办法登录和刷新token都会执行     * @param authentication     * @return     */    @Override    public Map<String, ?> convertUserAuthentication(Authentication authentication) {        Map<String, Object> response = new LinkedHashMap<>();        response.put("user_name", authentication.getName());        if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {            response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));        }        return response;    }    /**     * 将token map提取成UsernamePasswordAuthenticationToken对象     * 该办法先于convertUserAuthentication()执行     * 该办法仅刷新token执行     * @param map     * @return     */    @Override    public Authentication extractAuthentication(Map<String, ?> map) {        // 先判断token map中是否蕴含user_name字段,蕴含示意是零碎用户刷新token        // 因为两种用户token中都有user_id字段,所以先判断用户名        if (map.get("user_name") != null) {            Object principal = map.get("user_name");            Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);            if (this.userDetailsService != null) {                UserDetails user = this.userDetailsService.loadUserByUsername((String) map.get("user_name"));                authorities = user.getAuthorities();                principal = user;            }            return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);        } else if (map.get("user_id") != null) {            // token map中蕴含不蕴含user_name字段,示意是会员用户刷新token            Object principal = map.get("user_id");            Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);            if (this.userDetailsService != null) {                UserDetails user = this.userDetailsService.loadMemberUserById((Long) map.get("user_id"));                authorities = user.getAuthorities();                principal = user;            }            return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);        } else {            return null;        }    }    private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {        if (!map.containsKey("authorities")) {            return this.defaultAuthorities;        } else {            Object authorities = map.get("authorities");            if (authorities instanceof String) {                return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);            } else if (authorities instanceof Collection) {                return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection) authorities));            } else {                throw new IllegalArgumentException("Authorities must be either a String or a Collection");            }        }    }}

4、受权服务器AuthorizationServerConfig配置

4.1 注入自定义的DefaultTokenServices实现类bean

/**     *注入自定义的DefaultTokenServices实现类对象     * @param endpoints     * @return     */    @Bean    public DefaultTokenServices customTokenServices(AuthorizationServerEndpointsConfigurer endpoints) {        DefaultTokenServices tokenServices = new DefaultTokenServices();        tokenServices.setTokenStore(endpoints.getTokenStore());        tokenServices.setSupportRefreshToken(true);        tokenServices.setReuseRefreshToken(true);        tokenServices.setClientDetailsService(clientDetailsService);        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());        // access_token有效期:2个小时 -> 60*60*2        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);        // refresh_token有效期:12个小时 -> 60*60*12        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 12);        // 设置自定义的UserDetailsByNameOrIdServiceWrapper        if (userDetailsService != null) {            PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();            provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameOrIdServiceWrapper<>(userDetailsService));            tokenServices.setAuthenticationManager(new ProviderManager(Collections.singletonList(provider)));        }        return tokenServices;    }

4.2 注入自定义的CustomUserAuthenticationConverter

/**     * 注入自定义的CustomUserAuthenticationConverter     * @return     */    @Bean    public DefaultAccessTokenConverter defaultAccessTokenConverter() {        DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();        defaultAccessTokenConverter.setUserTokenConverter(new CustomUserAuthenticationConverter());        return defaultAccessTokenConverter;    }

4.3 将所有批改配置进configure(AuthorizationServerEndpointsConfigurer endpoints)

/**     * 配置受权(authorization)以及令牌(token)的拜访端点和令牌服务(token services)     */    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();        tokenEnhancers.add(tokenEnhancer());        tokenEnhancers.add(jwtAccessTokenConverter());        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);        endpoints                .authenticationManager(authenticationManager)                .accessTokenConverter(jwtAccessTokenConverter())                .tokenEnhancer(tokenEnhancerChain)                .userDetailsService(userDetailsService)                // refresh token有两种应用形式:重复使用(true)、非重复使用(false),默认为true                //   1、重复使用:access token过期刷新时, refresh token过期工夫未扭转,仍以首次生成的工夫为准                //   2、非重复使用:access token过期刷新时, refresh token过期工夫连续,在refresh token有效期内刷新便永不生效达到无需再次登录的目标                .reuseRefreshTokens(true)                // 将所有受权模式增加到配置中                .tokenGranter(createTokenGranter(endpoints))                // 配置自定义的CustomTokenServices实现类                .tokenServices(customTokenServices(endpoints))                // 配置自定义的用户认证转换器                .accessTokenConverter(defaultAccessTokenConverter());    }

5、刷新token的相干办法调用链

5.1 loadUserDetails()调用链

5.1 extractAuthentication()调用链

6、打完出工

6.1 @author
wx : owen2505
email : 975706304@qq.com