Пользовательский Spring Security генерирует JWT Token API

Spring Cloud

предыдущий постМы говорили о реализации пользовательской аутентификации при входе в систему путем добавления фильтров в Spring Security, и другие методы, такие как вход в систему с кодом подтверждения мобильного телефона, могут быть реализованы, но иногда мы хотим быть более гибкими. Например, для апплета мы уже аутентифицировали его в WeChat, и нам нужно только перейти в Spring Security, чтобы напрямую сгенерировать токен.

Для такого рода требований, хотя пользовательский метод входа в систему, описанный в предыдущей статье, также может быть реализован, мы надеемся сделать его простым, а усложнять простые вещи могут только дураки. следовательно,Мы реализовали простой и грубый API в Spring Security, то есть получить JWT Token напрямую через API

код выше

код простой

 @RequestMapping(value = "token", method = RequestMethod.POST)
    public ResponseEntity<OAuth2AccessToken> getUserToken(Principal principal, @RequestBody Map<String, String> parameters) {
        // 调用端需要被提供client_credentials
        if (!(principal instanceof Authentication)) {
            throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
        }

        // 保存请求参数
        String clientId = this.getClientId(principal);
        ClientDetails authenticatedClient = this.clientDetailsService.loadClientByClientId(clientId);
        TokenRequest tokenRequest = this.oAuth2RequestFactory.createTokenRequest(parameters, authenticatedClient);
        CustomAuthenticationToken authentication = customAuthenticationTokeService.createAuthenticationToken(clientId, parameters);
        
        // 生成token
        CustomTokenGranter tokenGranter = new CustomTokenGranter(
                tokenServiceFactory.customJwtTokenService(), clientDetailsService, authentication);

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

        if (token == null) {
            throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
        } else {
            return this.getResponse(token);
        }
    }
  1. Сначала проверьте, существует ли принципал, вызывающий должен предоставить client_credentials, иначе его нельзя будет вызвать.
  2. генерироватьCustomAuthenticationToken,CustomAuthenticationTokenПользовательский класс данных для сохранения параметров запроса
  3. Дело в том, чтобы пройтиCustomTokenGranterсгенерировать токен

CustomAuthenticationTokenВы можете определить его самостоятельно, если вы можете сохранить параметры запроса. В этой статье мы используем предыдущийCustomAuthenticationToken

public class CustomAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private String authType;

    private Map<String,String[]> authParams;

    private Object principal;

    private Object credentials;
}

Но мыcreateAuthenticationTokenНекоторые проверки параметров будут выполняться в файле , например, обязательны параметры Principle и auth_type, а также требуется auth_type=auth_finished". При необходимости вы также можете добавить некоторый пользовательский контент в CustomAuthenticationToken.

@Service
public class CustomAuthenticationTokenServiceImpl implements CustomAuthenticationTokeService {

    @Override
    public CustomAuthenticationToken createAuthenticationToken(String clientId, Map<String, String> params) {
        if (!params.containsKey("principal") || !params.containsKey("auth_type")) {
            return null;
        }

        if (params.get("auth_type").equals("auth_finished")) {

            List<GrantedAuthority> authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority("user"));
            Map<String, Object> detail = new HashMap<>(1);
            detail.put("principal", params.get("principal"));

            CustomAuthenticationToken token = new CustomAuthenticationToken(
                    params.get("auth_type"), params.get("username"), null, null, authorities);
            token.setDetails(detail);
            return token;
        }

        return null;
    }
}

CustomTokenGranterИспользуется для генерации токенов, самое главное неgrantметод, но конструктор, который мы передаем для создания токенаAuthorizationServerTokenServicesДобрый

public class CustomTokenGranter extends AbstractTokenGranter {
    private static final String GRANT_TYPE = "mini_app";
    private boolean allowRefresh;
    private Authentication authentication;

    public CustomTokenGranter(
            AuthorizationServerTokenServices tokenServices,
            ClientDetailsService clientDetailsService,
            Authentication authentication) {
        super(tokenServices, clientDetailsService, new DefaultOAuth2RequestFactory(clientDetailsService), GRANT_TYPE);
        this.authentication = authentication;
    }

    @Override
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        OAuth2AccessToken token = super.grant(grantType, tokenRequest);
        if (token != null) {
            DefaultOAuth2AccessToken noRefresh = new DefaultOAuth2AccessToken(token);
            if (!this.allowRefresh) {
                noRefresh.setRefreshToken((null));
            }
            token = noRefresh;
        }

        return token;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, authentication);
    }

    public void setAuthentication(Authentication authentication) {
        this.authentication = authentication;
    }

    public void setAllowRefresh(boolean allowRefresh) {
        this.allowRefresh = allowRefresh;
    }
}

AuthorizationServerTokenServices, в которых мы находимсяпредыдущий постОн был представлен, но я углублю свое впечатление, рассказав о нем еще раз.

  1. Сначала настройте токен
  2. Определите то, что вам нужно, чтобы определить вашу конфигурацию

Настроить токен

Логика генерации ядра токенаdefaultTokenServiceЕще есть Spring Security, но мы добавляемCustomTokenEnhancerИспользуется для добавления необходимого нам содержимого к содержимому токена,accessTokenConverterНастройте ключ нашего токена и открытый ключ (ссылка на генерацию ключа:Привет. Краткое введение .com/fear/ от 9 из 5 ах 2 ах 8 ...)

@Service
public class TokenServiceFactory {

    private TokenKeyConfig tokenKeyConfig;
    private ClientDetailsService clientDetailsService;

    @Autowired
    public TokenServiceFactory(
            TokenKeyConfig tokenKeyConfig,
            ClientDetailsService clientDetailsService) {
        this.tokenKeyConfig = tokenKeyConfig;
        this.clientDetailsService = clientDetailsService;
    }

    @Bean
    public AuthorizationServerTokenServices customJwtTokenService() {
        final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new CustomTokenEnhancer(), accessTokenConverter()));
        return defaultTokenService(tokenEnhancerChain);
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setAccessTokenConverter(new CustomAccessTokenConverter());

        final KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
                new ClassPathResource(tokenKeyConfig.getPath()), tokenKeyConfig.getPassword().toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair(tokenKeyConfig.getAlias()));

        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public org.springframework.security.oauth2.provider.token.TokenEnhancer tokenEnhancer() {
        return new TokenEnhancer();
    }

    private AuthorizationServerTokenServices defaultTokenService(TokenEnhancerChain tokenEnhancerChain) {
        final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
        defaultTokenServices.setClientDetailsService(clientDetailsService);
        return defaultTokenServices;
    }
}

Добавить пользовательский контент

Нам нужно заботиться о том, чтобыCustomTokenEnhancer, потому что нам нужно добавить наш пользовательский контент в токен, а также добавить информацию о роли и пользователе.

public class CustomTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        final Map<String, Object> additionalInfo = new HashMap<>();
        Set<GrantedAuthority> rolesInfo = new HashSet<>();

        Authentication userAuthentication = authentication.getUserAuthentication();

        // client credential认证,加入管理员角色
        if (authentication.isClientOnly()) {
            rolesInfo.add(new SimpleGrantedAuthority("admin"));
        }

        // 自定义认证,增加detail
        if (CustomAuthenticationToken.class.isAssignableFrom(userAuthentication.getClass())) {
            rolesInfo.addAll(userAuthentication.getAuthorities());
            additionalInfo.put("userInfo", userAuthentication.getDetails());
        }

        // 加入角色
        additionalInfo.put("authorities", rolesInfo.stream().map(auth -> auth.getAuthority()).toArray());
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        return accessToken;
    }
}

CustomAccessTokenConverterОбычно помещают все претензии в токен

@Component
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
        OAuth2Authentication authentication = super.extractAuthentication(claims);
        authentication.setDetails(claims);
        return authentication;
    }
}

увидеть эффект

  1. Сначала получите токен client_credential
  2. Используйте указанный выше токен в качестве заголовка (не забудьте добавить носитель), чтобы получить токен пользователя.

Пример использования

У нас есть серверная служба, которая хочет помочь апплету получить токены.В Spring Cloud ему нужно только использоватьgetUserTokenспособ получить токен

@FeignClient(name = "auth", configuration = AuthFeignConfigInterceptor.class)
@Service
public interface AuthClient {

    /**
     * get user token
     * @param parameters parameters
     * @return token
     */
    @RequestMapping(method = RequestMethod.POST, value = "/oauth/api/external/token")
    ResponseEntity<OAuth2AccessToken> getUserToken(@RequestBody Map<String, String> parameters);

}

AuthFeignConfigInterceptorпользователь вgetUserTokenДобавьте токен client_credential в запрос

public class AuthFeignConfigInterceptor implements RequestInterceptor {

    private static String TokenHeader = "authorization";
    private static String AccessTokenPrefix = "bearer ";

    private static Logger logger = LoggerFactory.getLogger(AuthFeignConfigInterceptor.class);

    @Autowired
    private ClientCredentialsResourceDetails clientCredentialsResourceDetails;


    @Override
    public void apply(RequestTemplate requestTemplate) {

        requestTemplate.header(TokenHeader, AccessTokenPrefix + getAccessTokenValue());
    }

    private String getAccessTokenValue() {
        try {
            logger.info("auth服务中获取basic access token");
            OAuth2AccessToken accessToken = this.getAccessTokenFromAuth();
            return accessToken.getValue();

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return this.getAccessTokenFromAuth().getValue();
        }
    }

    private OAuth2AccessToken getAccessTokenFromAuth() {
        ClientCredentialsAccessTokenProvider provider = new ClientCredentialsAccessTokenProvider();
        return provider.obtainAccessToken(clientCredentialsResourceDetails, new DefaultAccessTokenRequest());
    }
}

Application.yml необходимо настроить

security:
  oauth2:
    client:
      clientId: app
      clientSecret: testpassword
      accessTokenUri: http://localhost:5000/oauth/oauth/token
      grant-type: client_credentials
      scope: all

Копировать и вставить программирование

  1. Исходный код awesome-admingit ee.com/awesome-awesome…