从 Spring security oauth2 client 自动配置中获取 AccessToken

问题

使用 Spring security oauth2 client 实现授权码模式,可以不用手动拼接url请求code和token等信息,用户登录成功之后可以在SuccessHandler中获取当前用户的信息。如果还想获取用户信息以外的授权资源,必须要有AccessToken,要怎么获取呢?先看下如何获取用户信息

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

配置文件

spring:
  security:
    oauth2:
      client:
        provider:
          github:
            authorization-uri: https://github.com/login/oauth/authorize
            token-uri: https://github.com/login/oauth/access_token
            user-info-uri: https://api.github.com/user
            user-name-attribute: id
        registration:
          demo:
            client-id: xxxxx
            client-secret: xxxxx
            client-name: oauth2-demo
            provider: github
            scope: profile
            redirect-uri: http://localhost:8080/oauth2/callback
            client-authentication-method: basic
            authorization-grant-type: authorization_code
  http:
    log-request-details: true

logging:
  level:
    root: info
    web: debug

配置类

package com.example.demo.configurations;

import com.example.demo.handler.Oauth2AuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private Oauth2AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler;

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .oauth2Login()
                .redirectionEndpoint()
                .baseUri("/oauth2/callback")
                .and()
				.successHandler(oauth2AuthenticationSuccessHandler)
        ;
    }
}

SuccessHandler

package com.example.demo.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class Oauth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    public Oauth2AuthenticationSuccessHandler() {
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
        if (oAuth2User != null) {
            log.info(oAuth2User.toString());
        }
        response.sendRedirect("/index.html");
    }
}

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>OAuth2 Demo</title>
    </head>
    <body>
        <p>已登录</p>
    </body>
</html>

分析

关键代码 OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); ,在 IDEA 中查找跟Authentication相关的类:OAuth2AuthenticationTokenOAuth2AuthorizedClient,然后百度,找到一个非常有用的spring security 官方文档:链接,得到解决方案:使用 @RegisteredOAuth2AuthorizedClien 注解,登录成功后访问 http://localhost:8080/token 即可看到 AccessToken

HelloController

package com.example.demo.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@Slf4j
public class HelloController {
    @GetMapping("/token")
    @ResponseBody
    public String token(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) {
        return authorizedClient.getAccessToken().getTokenValue();
    }
}
以下是一个基于 Spring Security OAuth2 实现多人登录互踢下线的示例代码: 1. 配置 Spring Security OAuth2 在 Spring Boot 项目,可以通过在 application.properties 或 application.yml 配置以下内容来启用 Spring Security OAuth2: ``` spring.security.oauth2.client.registration.my-client-id.client-id=your-client-id spring.security.oauth2.client.registration.my-client-id.client-secret=your-client-secret spring.security.oauth2.client.registration.my-client-id.provider=my-provider spring.security.oauth2.client.registration.my-client-id.redirect-uri=http://localhost:8080/login/oauth2/code/my-client-id spring.security.oauth2.client.registration.my-client-id.scope=read,write ``` 2. 配置 Token 存储方式为 Redis 可以通过在 application.properties 或 application.yml 配置以下内容来启用 Redis 存储 Token: ``` spring.redis.host=your-redis-host spring.redis.port=your-redis-port spring.redis.password=your-redis-password spring.redis.database=0 spring.security.oauth2.provider.token.store-type=redis ``` 3. 在用户登录时生成唯一 Token 标识并存储到 Redis 可以通过编写一个自定义的 TokenStore 来实现在用户登录时生成唯一 Token 标识并存储到 Redis ,以下是一个示例代码: ``` @Component public class RedisTokenStore implements TokenStore { @Autowired private RedisTemplate<String, Object> redisTemplate; private static final String ACCESS_TOKEN_PREFIX = "access:"; private static final String AUTH_TO_ACCESS_PREFIX = "auth_to_access:"; private static final String AUTH_TO_REFRESH_PREFIX = "auth_to_refresh:"; private static final String CLIENT_ID_TO_ACCESS_PREFIX = "client_id_to_access:"; private static final String REFRESH_TOKEN_PREFIX = "refresh:"; @Override public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { // Implement this method } @Override public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { // Generate a unique token id String tokenId = UUID.randomUUID().toString(); // Store the token in Redis redisTemplate.opsForValue().set(ACCESS_TOKEN_PREFIX + tokenId, token); redisTemplate.opsForValue().set(AUTH_TO_ACCESS_PREFIX + authenticationKeyGenerator.extractKey(authentication), tokenId); redisTemplate.opsForValue().set(CLIENT_ID_TO_ACCESS_PREFIX + token.getClientId(), tokenId); if (token.getRefreshToken() != null && token.getRefreshToken().getValue() != null) { redisTemplate.opsForValue().set(AUTH_TO_REFRESH_PREFIX + authenticationKeyGenerator.extractKey(authentication), token.getRefreshToken().getValue()); redisTemplate.opsForValue().set(REFRESH_TOKEN_PREFIX + token.getRefreshToken().getValue(), tokenId); } } // Implement other methods } ``` 4. 在用户退出登录时删除 Redis Token 标识 可以通过编写一个 LogoutHandler 来实现在用户退出登录时删除 Redis Token 标识,以下是一个示例代码: ``` @Component public class RedisLogoutHandler implements LogoutHandler { @Autowired private RedisTemplate<String, Object> redisTemplate; private static final String ACCESS_TOKEN_PREFIX = "access:"; @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { // Get the token from the request String token = extractToken(request); // Delete the token from Redis if (StringUtils.isNotBlank(token)) { redisTemplate.delete(ACCESS_TOKEN_PREFIX + token); } } private String extractToken(HttpServletRequest request) { // Implement this method } } ``` 5. 在用户登录时检查 Redis 是否存在相同用户的 Token 标识 可以通过编写一个自定义的 ConcurrentSessionControlStrategy 来实现在用户登录时检查 Redis 是否存在相同用户的 Token 标识,以下是一个示例代码: ``` @Component public class RedisConcurrentSessionControlStrategy extends ConcurrentSessionControlStrategy { @Autowired private RedisTemplate<String, Object> redisTemplate; private static final String ACCESS_TOKEN_PREFIX = "access:"; @Override public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException { // Get the user id from the authentication String userId = getUserId(authentication); // Get the token from the request String token = extractToken(request); // Check if there is another active session for this user if (StringUtils.isNotBlank(userId)) { String existingToken = (String) redisTemplate.opsForValue().get(userId); if (StringUtils.isNotBlank(existingToken) && !existingToken.equals(token)) { // Delete the existing token from Redis redisTemplate.delete(ACCESS_TOKEN_PREFIX + existingToken); // Throw an exception to indicate that the user has been logged out throw new ConcurrentSessionControlStrategy.SessionAuthenticationException("User " + userId + " has been logged out by another session."); } // Store the token in Redis if (StringUtils.isNotBlank(token)) { redisTemplate.opsForValue().set(userId, token); } } // Call the superclass method to continue the authentication process super.onAuthentication(authentication, request, response); } private String getUserId(Authentication authentication) { // Implement this method } private String extractToken(HttpServletRequest request) { // Implement this method } } ``` 通过以上代码,可以实现基于 Spring Security OAuth2 的多人登录互踢下线功能。需要注意的是,以上代码仅作为示例,实际应用可能需要根据具体的业务需求进行修改和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值