mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 04:08:43 +08:00 
			
		
		
		
	完成 yudao-sso-demo-by-code 实现 token 过滤器
This commit is contained in:
		@@ -1,7 +1,8 @@
 | 
			
		||||
package cn.iocoder.yudao.ssodemo.client;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.dto.OAuth2AccessTokenRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
 | 
			
		||||
import org.springframework.core.ParameterizedTypeReference;
 | 
			
		||||
import org.springframework.http.*;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
@@ -65,6 +66,26 @@ public class OAuth2Client {
 | 
			
		||||
        return exchange.getBody();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) {
 | 
			
		||||
        // 1.1 构建请求头
 | 
			
		||||
        HttpHeaders headers = new HttpHeaders();
 | 
			
		||||
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
 | 
			
		||||
        headers.set("tenant-id", TENANT_ID.toString());
 | 
			
		||||
        addClientHeader(headers);
 | 
			
		||||
        // 1.2 构建请求参数
 | 
			
		||||
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
 | 
			
		||||
        body.add("token", token);
 | 
			
		||||
 | 
			
		||||
        // 2. 执行请求
 | 
			
		||||
        ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange(
 | 
			
		||||
                BASE_URL + "/check-token",
 | 
			
		||||
                HttpMethod.POST,
 | 
			
		||||
                new HttpEntity<>(body, headers),
 | 
			
		||||
                new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
 | 
			
		||||
        Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
 | 
			
		||||
        return exchange.getBody();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void addClientHeader(HttpHeaders headers) {
 | 
			
		||||
        // client 拼接,需要 BASE64 编码
 | 
			
		||||
        String client = CLIENT_ID + ":" + CLIENT_SECRET;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package cn.iocoder.yudao.ssodemo.client.dto;
 | 
			
		||||
package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 校验令牌 Response DTO
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class OAuth2CheckTokenRespDTO {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户编号
 | 
			
		||||
     */
 | 
			
		||||
    @JsonProperty("user_id")
 | 
			
		||||
    private Long userId;
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户类型
 | 
			
		||||
     */
 | 
			
		||||
    @JsonProperty("user_type")
 | 
			
		||||
    private Integer userType;
 | 
			
		||||
    /**
 | 
			
		||||
     * 租户编号
 | 
			
		||||
     */
 | 
			
		||||
    @JsonProperty("tenant_id")
 | 
			
		||||
    private Long tenantId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 客户端编号
 | 
			
		||||
     */
 | 
			
		||||
    @JsonProperty("client_id")
 | 
			
		||||
    private String clientId;
 | 
			
		||||
    /**
 | 
			
		||||
     * 授权范围
 | 
			
		||||
     */
 | 
			
		||||
    private List<String> scopes;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 访问令牌
 | 
			
		||||
     */
 | 
			
		||||
    @JsonProperty("access_token")
 | 
			
		||||
    private String accessToken;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 过期时间
 | 
			
		||||
     *
 | 
			
		||||
     * 时间戳 / 1000,即单位:秒
 | 
			
		||||
     */
 | 
			
		||||
    private Long exp;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.ssodemo.controller;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.dto.OAuth2AccessTokenRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestParam;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
package cn.iocoder.yudao.ssodemo.controller;
 | 
			
		||||
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/user")
 | 
			
		||||
public class UserController {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获得当前登录用户的基本信息
 | 
			
		||||
     *
 | 
			
		||||
     * @return TODO
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/get")
 | 
			
		||||
    public String getUser() {
 | 
			
		||||
        return "";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +1,31 @@
 | 
			
		||||
package cn.iocoder.yudao.ssodemo.framework;
 | 
			
		||||
package cn.iocoder.yudao.ssodemo.framework.config;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.framework.core.TokenAuthenticationFilter;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.http.HttpMethod;
 | 
			
		||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 | 
			
		||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 | 
			
		||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
 | 
			
		||||
@Configuration
 | 
			
		||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 | 
			
		||||
 | 
			
		||||
//    /**
 | 
			
		||||
//     * Token 认证过滤器 Bean
 | 
			
		||||
//     */
 | 
			
		||||
//    @Bean
 | 
			
		||||
//    public TokenAuthenticationFilter authenticationTokenFilter(OAuth2Client oauth2Client) {
 | 
			
		||||
//        return new TokenAuthenticationFilter(oauth2Client);
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private TokenAuthenticationFilter tokenAuthenticationFilter;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void configure(HttpSecurity httpSecurity) throws Exception {
 | 
			
		||||
        // 设置 URL 安全权限
 | 
			
		||||
        httpSecurity.csrf().disable() // 禁用 CSRF 保护
 | 
			
		||||
                .authorizeRequests()
 | 
			
		||||
                // 1. 静态资源,可匿名访问
 | 
			
		||||
@@ -19,5 +35,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 | 
			
		||||
                // last. 兜底规则,必须认证
 | 
			
		||||
                .and().authorizeRequests()
 | 
			
		||||
                .anyRequest().authenticated();
 | 
			
		||||
 | 
			
		||||
        // 添加 Token Filter
 | 
			
		||||
        httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
package cn.iocoder.yudao.ssodemo.framework.core;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 登录用户信息
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class LoginUser {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户编号
 | 
			
		||||
     */
 | 
			
		||||
    private Long id;
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户类型
 | 
			
		||||
     */
 | 
			
		||||
    private Integer userType;
 | 
			
		||||
    /**
 | 
			
		||||
     * 租户编号
 | 
			
		||||
     */
 | 
			
		||||
    private Long tenantId;
 | 
			
		||||
    /**
 | 
			
		||||
     * 授权范围
 | 
			
		||||
     */
 | 
			
		||||
    private List<String> scopes;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,107 @@
 | 
			
		||||
package cn.iocoder.yudao.ssodemo.framework.core;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
 | 
			
		||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
 | 
			
		||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 | 
			
		||||
import org.springframework.security.core.Authentication;
 | 
			
		||||
import org.springframework.security.core.context.SecurityContextHolder;
 | 
			
		||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import org.springframework.util.StringUtils;
 | 
			
		||||
import org.springframework.web.filter.OncePerRequestFilter;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import javax.servlet.FilterChain;
 | 
			
		||||
import javax.servlet.ServletException;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Token 过滤器,验证 token 的有效性
 | 
			
		||||
 * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
 | 
			
		||||
 *
 | 
			
		||||
 * @author 芋道源码
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private OAuth2Client oauth2Client;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
 | 
			
		||||
                                    FilterChain filterChain) throws ServletException, IOException {
 | 
			
		||||
        // 1. 获得访问令牌
 | 
			
		||||
        String token = obtainAuthorization(request);
 | 
			
		||||
        if (StringUtils.hasText(token)) {
 | 
			
		||||
            // 2. 基于 token 构建登录用户
 | 
			
		||||
            LoginUser loginUser = buildLoginUserByToken(token);
 | 
			
		||||
            // 3. 设置当前用户
 | 
			
		||||
            if (loginUser != null) {
 | 
			
		||||
                setLoginUser(loginUser, request);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 继续过滤链
 | 
			
		||||
        filterChain.doFilter(request, response);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private LoginUser buildLoginUserByToken(String token) {
 | 
			
		||||
        try {
 | 
			
		||||
            CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token);
 | 
			
		||||
            OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData();
 | 
			
		||||
            if (accessToken == null) {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            // 构建登录用户
 | 
			
		||||
            return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
 | 
			
		||||
                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
 | 
			
		||||
        } catch (Exception exception) {
 | 
			
		||||
            // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 从请求 Header 中,获得访问令牌
 | 
			
		||||
     *
 | 
			
		||||
     * @param request 请求
 | 
			
		||||
     * @return 访问令牌
 | 
			
		||||
     */
 | 
			
		||||
    private static String obtainAuthorization(HttpServletRequest request) {
 | 
			
		||||
        String authorization = request.getHeader("Authentication");
 | 
			
		||||
        if (!StringUtils.hasText(authorization)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        int index = authorization.indexOf("Bearer ");
 | 
			
		||||
        if (index == -1) { // 未找到
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return authorization.substring(index + 7).trim();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置当前用户
 | 
			
		||||
     *
 | 
			
		||||
     * @param loginUser 登录用户
 | 
			
		||||
     * @param request 请求
 | 
			
		||||
     */
 | 
			
		||||
    private static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
 | 
			
		||||
        // 创建 Authentication,并设置到上下文
 | 
			
		||||
        Authentication authentication = buildAuthentication(loginUser, request);
 | 
			
		||||
        SecurityContextHolder.getContext().setAuthentication(authentication);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
 | 
			
		||||
        // 创建 UsernamePasswordAuthenticationToken 对象
 | 
			
		||||
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
 | 
			
		||||
                loginUser, null, Collections.emptyList());
 | 
			
		||||
        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
 | 
			
		||||
        return authenticationToken;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -19,14 +19,57 @@
 | 
			
		||||
				+ '&redirect_uri=' + redirectUri
 | 
			
		||||
				+ '&response_type=' + responseType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $(function () {
 | 
			
		||||
      const accessToken = localStorage.getItem('ACCESS-TOKEN');
 | 
			
		||||
      // 情况一:未登录
 | 
			
		||||
			if (!accessToken) {
 | 
			
		||||
        $('#noLoginDiv').css("display", "block");
 | 
			
		||||
        return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
      // 情况二:已登录
 | 
			
		||||
      $('#yesLoginDiv').css("display", "block");
 | 
			
		||||
      $('#accessTokenSpan').html(accessToken);
 | 
			
		||||
      // 获得登录用户的信息
 | 
			
		||||
      $.ajax({
 | 
			
		||||
        url: "http://127.0.0.1:18080/user/get",
 | 
			
		||||
        method: 'GET',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Authentication': 'Bearer ' + accessToken
 | 
			
		||||
        },
 | 
			
		||||
        success: function (result) {
 | 
			
		||||
          if (result.code !== 0) {
 | 
			
		||||
            alert('获得个人信息失败,原因:' + result.msg)
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          $('nicknameSpan').html(result.data.nickname);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
	</script>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 -->
 | 
			
		||||
<div>
 | 
			
		||||
	您未登录,点击 <a href="#" onclick="ssoLogin()">跳转 </a> SSO 单点登录
 | 
			
		||||
</div>
 | 
			
		||||
	<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 -->
 | 
			
		||||
	<div id="noLoginDiv" style="display: none">
 | 
			
		||||
		您未登录,点击 <a href="#" onclick="ssoLogin()">跳转 </a> SSO 单点登录
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
 | 
			
		||||
	<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
 | 
			
		||||
	<div id="yesLoginDiv" style="display: none">
 | 
			
		||||
		您已登录!点击 <a href="#" onclick="ssoLogin()">退出 </a> 系统 <br />
 | 
			
		||||
		昵称:<span id="nicknameSpan"> 加载中... </span> <br />
 | 
			
		||||
		访问令牌:<span id="accessTokenSpan"> 加载中... </span> <br />
 | 
			
		||||
	</div>
 | 
			
		||||
</body>
 | 
			
		||||
<style>
 | 
			
		||||
    body { /** 页面居中 */
 | 
			
		||||
        border-radius: 20px;
 | 
			
		||||
        height: 350px;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        left: 50%;
 | 
			
		||||
        top: 50%;
 | 
			
		||||
        transform: translate(-50%,-50%);
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user