feat: 新增双 token 鉴权实现

11111
winter 1 year ago
parent 38bb428c59
commit c1e2a62527

@ -51,12 +51,6 @@
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

@ -4,10 +4,10 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeploymentTestApplication {
public class TeamModelExtensionApplication {
public static void main(String[] args) {
SpringApplication.run(DeploymentTestApplication.class, args);
SpringApplication.run(TeamModelExtensionApplication.class, args);
}
}

@ -0,0 +1,72 @@
package cn.teammodel.config.exception;
/**
*
*
* @author winter
*/
public final class ServiceException extends RuntimeException
{
private static final long serialVersionUID = 1L;
/**
*
*/
private Integer code;
/**
*
*/
private String message;
/**
*
*/
private String detailMessage;
/**
*
*/
public ServiceException()
{
}
public ServiceException(String message)
{
this.message = message;
}
public ServiceException(String message, Integer code)
{
this.message = message;
this.code = code;
}
public String getDetailMessage()
{
return detailMessage;
}
@Override
public String getMessage()
{
return message;
}
public Integer getCode()
{
return code;
}
public ServiceException setMessage(String message)
{
this.message = message;
return this;
}
public ServiceException setDetailMessage(String detailMessage)
{
this.detailMessage = detailMessage;
return this;
}
}

@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController;
public class HelloController {
@GetMapping("hello")
@PreAuthorize("hasAuthority('IES')")
@PreAuthorize("@ss.hasRole('admin')")
public R hello() {
System.out.println(SecurityContextHolder.getContext().getAuthentication());
return new R(200, "success","hello world");

@ -0,0 +1,50 @@
package cn.teammodel.model.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* @author winter
* @create 2023-11-09 15:28
*/
@Data
public class TmdUserDetail implements UserDetails {
private User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return true;
}
}

@ -0,0 +1,24 @@
package cn.teammodel.model.entity;
import lombok.Data;
import lombok.ToString;
import java.util.Set;
/**
* @author winter
* @create 2023-11-09 15:43
*/
@Data
@ToString
public class User {
private String id;
private String name;
private String picture;
private String standard;
private String scope;
private String website;
private String area;
private Set<String> roles;
private Set<String> permissions;
}

@ -3,6 +3,7 @@ package cn.teammodel.security;
import cn.teammodel.security.filter.AuthInnerTokenFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
@ -20,6 +21,7 @@ import javax.annotation.Resource;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfiguration {
@Resource
private AuthInnerTokenFilter authInnerTokenFilter;

@ -1,5 +1,17 @@
package cn.teammodel.security.filter;
import cn.teammodel.config.exception.ServiceException;
import cn.teammodel.model.entity.TmdUserDetail;
import cn.teammodel.security.utils.JwtTokenUtil;
import cn.teammodel.security.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@ -8,6 +20,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
/**
* x-auth-authToken filter
@ -15,9 +28,28 @@ import java.io.IOException;
* @create 2023-11-09 10:43
*/
@Component
@Slf4j
public class AuthInnerTokenFilter extends OncePerRequestFilter {
@Autowired
JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
// 进入此过滤器说明 OIDC 认证成功,则验证 authToken
// 验证 authToken 合法
TmdUserDetail tmdUserDetail = jwtTokenUtil.getValidUserDetail(request);
if (tmdUserDetail == null) {
log.error("authToken authentication failed");
throw new ServiceException("无权限");
}
System.out.println(tmdUserDetail.getUser());
// 组装 authToken 的 jwt 进 authentication
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
UsernamePasswordAuthenticationToken finalAuthentication = new UsernamePasswordAuthenticationToken(tmdUserDetail, null, authorities);
context.setAuthentication(finalAuthentication);
filterChain.doFilter(request, response);
}
}

@ -0,0 +1,174 @@
package cn.teammodel.security.service;
import java.util.Set;
import cn.teammodel.model.entity.User;
import cn.teammodel.security.utils.SecurityUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
/**
* ssSpringSecurity <br/>
* <p>
* 1. IES : hasAuth <br/>
* 2. role (authToken ): hasRole <br/>
* 3. permission (authToken ): hasPermi <br/>
* </p>
* @author ruoyi
*/
@Service("ss")
public class PermissionService
{
/** 所有权限标识 */
private static final String ALL_PERMISSION = "*:*:*";
/** 管理员角色权限标识 */
private static final String SUPER_ADMIN = "admin";
private static final String ROLE_DELIMETER = ",";
private static final String PERMISSION_DELIMETER = ",";
/**
* access_token auth
* @param auth:
* @return: boolean
* @author: winter
* @date: 2023/11/10 10:05
* @description:
*/
public boolean hasAuth(String auth)
{
if (StringUtils.isEmpty(auth))
{
return false;
}
Authentication authentication = SecurityUtils.getAuthentication();
if (authentication == null || CollectionUtils.isEmpty(authentication.getAuthorities()))
{
return false;
}
return authentication.getAuthorities().contains(auth);
}
/**
*
*
* @param permission
* @return
*/
public boolean hasPermi(String permission)
{
if (StringUtils.isEmpty(permission))
{
return false;
}
User loginUser = SecurityUtils.getLoginUser();
if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
return false;
}
return loginUser.getPermissions().contains(permission);
}
/**
* hasPermi
*
* @param permission
* @return
*/
public boolean lacksPermi(String permission)
{
return !hasPermi(permission);
}
/**
*
*
* @param permissions PERMISSION_DELIMETER
* @return
*/
public boolean hasAnyPermi(String permissions)
{
if (StringUtils.isEmpty(permissions))
{
return false;
}
User loginUser = SecurityUtils.getLoginUser();
if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
return false;
}
Set<String> authorities = loginUser.getPermissions();
for (String permission : permissions.split(PERMISSION_DELIMETER))
{
if (permission != null && authorities.contains(permission))
{
return true;
}
}
return false;
}
/**
*
*
* @param role
* @return
*/
public boolean hasRole(String role)
{
if (StringUtils.isEmpty(role)) {
return false;
}
User loginUser = SecurityUtils.getLoginUser();
if (loginUser == null || CollectionUtils.isEmpty(loginUser.getRoles()))
{
return false;
}
return loginUser.getRoles().contains(role);
}
/**
* isRole
*
* @param role
* @return
*/
public boolean lacksRole(String role)
{
return !hasRole(role);
}
/**
*
*
* @param roles ROLE_NAMES_DELIMETER
* @return
*/
public boolean hasAnyRoles(String roles)
{
if (StringUtils.isEmpty(roles))
{
return false;
}
User loginUser = SecurityUtils.getLoginUser();
if (loginUser == null || CollectionUtils.isEmpty(loginUser.getRoles()))
{
return false;
}
for (String role : roles.split(ROLE_DELIMETER))
{
if (hasRole(role))
{
return true;
}
}
return false;
}
}

@ -1,16 +1,21 @@
package cn.teammodel.security.utils;
import cn.teammodel.model.entity.User;
import cn.teammodel.model.entity.TmdUserDetail;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author winter
@ -25,23 +30,21 @@ public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.tokenHead}")
private String tokenHead;
private Integer expiration = 30;
/**
* token
* @param userDetails userDetails
* @return token
*/
@Deprecated
public String generateToken(UserDetails userDetails){
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED,new Date());
return generateToken(claims);
}
@Deprecated
private String generateToken(Map<String,Object> claims){
return Jwts.builder()
.setClaims(claims)
@ -63,6 +66,7 @@ public class JwtTokenUtil {
* @param token token
* @return ,null
*/
@Deprecated
public String getUsernameFromToken(String token) {
String username = null;
try {
@ -79,15 +83,14 @@ public class JwtTokenUtil {
* @param token token
* @return claimsnull
*/
private Claims getClaimsFromToken(String token){
private Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.setSigningKey(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"))
.parseClaimsJws(token)
.getBody();
} catch (Exception e){
e.printStackTrace();
} catch (Exception e) {
log.info("jwt解析出错:{}",token);
}
return claims;
@ -96,13 +99,21 @@ public class JwtTokenUtil {
/**
* token
* @param token token
* @param userDetails
* @return truefalse
*/
public boolean validateToken(String token) {
return !isTokenExpired(token);
}
public boolean validateToken(HttpServletRequest request) {
String token = request.getHeader("x-auth-AuthToken");
if (StringUtils.isBlank(token)) {
return false;
}
return validateToken(token);
}
private boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
@ -112,4 +123,60 @@ public class JwtTokenUtil {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* token , null
* @param request:
* @return: cn.teammodel.model.entity.TmdUserDetail
* @author: winter
* @date: 2023/11/10 10:24
* @description:
*/
public TmdUserDetail getValidUserDetail(HttpServletRequest request) {
String token = request.getHeader("x-auth-AuthToken");
if (StringUtils.isBlank(token)) {
return null;
}
Claims claims = getClaimsFromToken(token);
if (claims == null) {
return null;
}
// 组装 TmdUserDetail
TmdUserDetail tmdUserDetail = new TmdUserDetail();
User user = new User();
String id = claims.getSubject();
user.setId(id);
user.setName(claims.get("name") == null ? null : claims.get("name", String.class));
user.setPicture(claims.get("picture") == null ? null : claims.get("picture", String.class));
user.setStandard(claims.get("standard") == null ? null : claims.get("standard", String.class));
user.setScope(claims.get("scope") == null ? null : claims.get("scope", String.class));
user.setWebsite(claims.get("website") == null ? null : claims.get("website", String.class));
user.setArea(claims.get("area") == null ? null : claims.get("area", String.class));
// 取出 roles 和 permissions
Set<String> roleSet = convertToArray(claims.get("roles"));
Set<String> permissionSet = convertToArray(claims.get("permissions"));
user.setRoles(roleSet);
user.setPermissions(permissionSet);
tmdUserDetail.setUser(user);
return tmdUserDetail;
}
@SuppressWarnings("unchecked")
private Set<String> convertToArray(Object o) {
if (o == null) {
return Collections.emptySet();
}
if (o instanceof String) {
if (StringUtils.isNotBlank((String)o)) {
return Arrays.stream(((String) o).split(" ")).collect(Collectors.toSet());
}
return Collections.emptySet();
}
if (o instanceof Collection) {
return new HashSet<>(((Collection<String>) o));
}
return Collections.emptySet();
}
}

@ -0,0 +1,107 @@
package cn.teammodel.security.utils;
import cn.teammodel.config.exception.ServiceException;
import cn.teammodel.model.entity.User;
import cn.teammodel.model.entity.TmdUserDetail;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
*
*
* @author winter
*/
public class SecurityUtils
{
/**
* ID
**/
public static String getUserId()
{
try
{
return getLoginUser().getId();
}
catch (Exception e)
{
throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED.value());
}
}
/**
*
**/
public static String getUsername()
{
try
{
return getLoginUser().getName();
}
catch (Exception e)
{
throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED.value());
}
}
/**
*
**/
public static User getLoginUser()
{
try
{
return ((TmdUserDetail) getAuthentication().getPrincipal()).getUser();
}
catch (Exception e)
{
throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED.value());
}
}
/**
* Authentication
*/
public static Authentication getAuthentication()
{
return SecurityContextHolder.getContext().getAuthentication();
}
/**
* BCryptPasswordEncoder
*
* @param password
* @return
*/
public static String encryptPassword(String password)
{
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.encode(password);
}
/**
*
*
* @param rawPassword
* @param encodedPassword
* @return
*/
public static boolean matchesPassword(String rawPassword, String encodedPassword)
{
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.matches(rawPassword, encodedPassword);
}
/**
*
*
* @param userId ID
* @return
*/
public static boolean isAdmin(Long userId)
{
return userId != null && 1L == userId;
}
}

@ -12,3 +12,6 @@ spring:
jwt:
issuer-uri: https://login.partner.microsoftonline.cn/4807e9cf-87b8-4174-aa5b-e76497d7392b/v2.0
audiences: 72643704-b2e7-4b26-b881-bd5865e7a7a5
jwt:
secret: fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA=

@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DeploymentTestApplicationTests {
class TeamModelExtensionApplicationTests {
@Test
void contextLoads() {

@ -0,0 +1,25 @@
package cn.teammodel;
import org.junit.jupiter.api.Test;
/**
* @author winter
* @create 2023-11-10 10:42
*/
public class TestWithoutSpring {
@Test
public void testJwtDecode() {
// Claims claims = null;
// try {
// Jwts.parser()
//
//
//// claims = Jwts.parser()
//// .setSigningKey("fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA=")
//// .parseClaimsJws("eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0LnRlYW1tb2RlbC5jbiIsInN1YiI6IjE1OTUzMjEzNTQiLCJhenAiOiJoYmNuIiwiZXhwIjoxNjk5NTg2MzM1LCJuYW1lIjoi572X6ICB5biIIiwicGljdHVyZSI6Imh0dHBzOi8vY29yZXN0b3JhZ2VzZXJ2aWNlLmJsb2IuY29yZS5jaGluYWNsb3VkYXBpLmNuL2FjY291bnQvYXZhdGFyLzE1OTUzMjEzNTQiLCJyb2xlcyI6WyJ0ZWFjaGVyIiwiYWRtaW4iLCJhcmVhIl0sInBlcm1pc3Npb25zIjpbXSwic3RhbmRhcmQiOiJzdGFuZGFyZDEiLCJzY29wZSI6InRlYWNoZXIiLCJhcmVhIjoiNjllM2Q0MTMtNTBhMS00ZjVlLTg0NGEtZTBmN2M5NjIyZWEzIiwid2Vic2l0ZSI6IklFUyJ9.4NdqwDdDQcyberEAirX0srOIb1ADXLJfP-a9qNXb0yw")
//// .getBody();
// } catch (Exception e) {
// e.printStackTrace();
// }
}
}
Loading…
Cancel
Save