spring security oauth2免密登录

/ Spring Cloud / 没有评论 / 140浏览

在使用spring cloud全家桶时,不可避免的使用oauth2,抽取一个认证服务。有时候,会有一个蛋疼的需求——不需要密码登录。

背景

大概去年上半年,做spring cloud时,有同事让帮忙加一个无需密码登录的接口,即通过usernameclient_idclient_secret认证,返回access_token

一年之后的今天,又有同事提出这样的需求。因此,这里做一个总结。

接口

下面是一个无密码登录的接口,根据usernameclient_idclient_secret认证,获取OAuth2AccessToken

/**
 * 无密码登录服务
 *
 * @author 奔波儿灞
 * @since 1.0
 */
public interface NoPasswordLoginService {

    /**
     * 无密码登录
     *
     * @param username username
     * @param clientId clientId
     * @param clientSecret clientSecret
     * @return OAuth2AccessToken
     */
    OAuth2AccessToken noPasswordLogin(String username, String clientId, String clientSecret);

}

实现

无密码登录服务实现,如果对spring security了解一点,应该知道自定义UserDetailsServiceClientDetailsService来提供用户信息、oauth2信息。

package tk.fishfish.authserver.oauth.service.impl;

import tk.fishfish.authserver.oauth.dto.User;
import tk.fishfish.authserver.oauth.service.UserLoginService;
import tk.fishfish.authserver.oauth.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 * 无密码登录服务实现
 *
 * @author 奔波儿灞
 * @since 1.0
 */
@Service
public class NoPasswordLoginService implements NoPasswordLoginService {

    @Autowired
    private UserDetailsService customUserDetailsService;

    @Autowired
    private ClientDetailsService customClientDetailsService;

    @Autowired
    private DefaultTokenServices defaultTokenServices;

    @Override
    public OAuth2AccessToken noPasswordLogin(String username, String clientId, String clientSecret) {
        // 检验client_id、client_secret是否合法
        ClientDetails clientDetails = loadClientDetails(clientId, clientSecret);

        // 获取用户信息
        UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);

        // 创建OAuth2Request
        Set<String> responseTypes = new HashSet<>();
        // 这里是oauth2授权码模式
        responseTypes.add("code");
        OAuth2Request oAuth2Request = new OAuth2Request(Collections.emptyMap(), clientId,
                userDetails.getAuthorities(), true, clientDetails.getScope(),
                clientDetails.getResourceIds(), null, responseTypes, Collections.emptyMap());
        // 创建UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        // 创建OAuth2Authentication
        OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken);

        // 生成token
        return defaultTokenServices.createAccessToken(auth);
    }

    private ClientDetails loadClientDetails(String clientId, String clientSecret) {
        ClientDetails clientDetails = customClientDetailsService.loadClientByClientId(clientId);
        if (!clientDetails.getClientSecret().equals(clientSecret)) {
            throw new NoSuchClientException("clientId or clientSecret invalid.");
        }
        return clientDetails;
    }

}

最后,需要定义DefaultTokenServices,来获取管理token。

package tk.fishfish.oauth.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;

/**
 * token配置
 *
 * @author 奔波儿灞
 * @since 1.0
 */
@Configuration
public class TokenConfiguration {

    /**
     * 注册DefaultTokenServices
     *
     * @param tokenStore TokenStore
     * @param clientDetailsService ClientDetailsService
     * @return DefaultTokenServices
     */
    @Bean
    @Primary
    public DefaultTokenServices tokenServices(TokenStore tokenStore, ClientDetailsService clientDetailsService) {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore);
        defaultTokenServices.setSupportRefreshToken(true);
        defaultTokenServices.setClientDetailsService(clientDetailsService);
        return defaultTokenServices;
    }

}

其中,TokenStore通常使用redis存储。

测试

获取token

获取token

token换取用户信息

用户信息

总结

我也忘记当时是如何找到通过org.springframework.security.oauth2.provider.token.DefaultTokenServices来创建token了,只记得是围绕org.springframework.security.oauth2.provider.token.TokenStore来看代码,找生成access_token的代码。

代码