Spring Boot 集成 JWT 實現單點登錄授權


使用步驟如下:
1. 添加Gradle依賴:

dependencies {
implementation 'com.auth0:java-jwt:3.3.0'
implementation('org.springframework.boot:spring-boot-starter-aop')
}
2. 登錄檢驗時,使用JWT生成Token令牌(我這里登錄用戶名是email)。

/**
* 登錄檢驗方法。
* @param user
* @return
*/
public String login(User user) {
// 登錄檢驗邏輯 TODO

//登錄檢驗成功,生成token令牌
String token = tokenService.generateToken(userRepository.findUserByMail(user.getMail()));

//自定義返回值
return null;
}
一、生成和校驗Token。
1)生成和校驗token令牌的服務類。

/**
* 使用JWT作為Token實現。
*
* @author LEEMER
* Create Date: 2019-05-20
*/
@Service
public class TokenService {
private static final Logger LOGGER = LoggerFactory.getLogger(TokenService.class);

private TokenConfigBean tokenConfigBean;

/**
* Token加密算法。
*/
private Algorithm algorithm;

/**
* Token認證對象。
*/
private JWTVerifier verifier;

private UserRepository userRepository;

public TokenService(TokenConfigBean tokenConfigBean,
Algorithm algorithm,
JWTVerifier verifier,
UserRepository userRepository) {
this.tokenConfigBean = tokenConfigBean;
this.algorithm = algorithm;
this.verifier = verifier;
this.userRepository = userRepository;
}

public String generateToken(User user) {
JWTCreator.Builder builder = JWT.create().withClaim("mail", user.getMail());

builder.withClaim("expiredTime", System.currentTimeMillis()
+ tokenConfigBean.getExpiredTime() * 60 * 1000);

return builder.sign(algorithm);
}

public boolean isValid(String token) {
DecodedJWT jwt;
try {
jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
LOGGER.debug("該Token解碼失敗:" + token);
return false;
}
long expiredTime = jwt.getClaim("expiredTime").asLong();
if (expiredTime < System.currentTimeMillis()) {
LOGGER.debug("該Token已過期:" + expiredTime);
return false;
}
return true;
}

public String resetExpiredTime(String token) {
DecodedJWT jwt;
try {
jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
return "";
}
User user = userRepository.findUserByMail(jwt.getClaim("mail").asString());
return generateToken(user);
}

public String getValueByMail(String token, String key) {
var trueToken = token.startsWith("Bearer ") ? token.substring(7) : token;
DecodedJWT jwt;
try {
jwt = verifier.verify(trueToken);
} catch (JWTVerificationException exception){
LOGGER.debug("目標Token解析失敗:" + trueToken);
return null;
}
return jwt.getClaim(key).asString();
}

public String getValueByMail(String token, int mail) {
return getValueByMail(token, String.valueOf(mail));
}

public <T> List<T> getListValueByMail(String token, String mail, Class<T> cls) {
DecodedJWT jwt;
try {
jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
return null;
}
return jwt.getClaim(mail).asList(cls);
}

public <T> List<T> getListValueByMail(String token, int mail, Class<T> cls) {
return getListValueByMail(token, String.valueOf(mail), cls);
}

public User getUser(String token) {
var mail = getValueByMail(token, "mail");
if (StringUtils.isBlank(mail)) {
LOGGER.error("[getUser] 從該Token中無法提取有效郵箱:{}", token);
return null;
}
return userRepository.findUserByMail(mail);
}
}
2)Token配置Bean:設置token過期時間、混淆。

/**
* @author LEEMER
* Create Date: 2019-05-20
*/
@Configuration
public class TokenConfigBean {

/**
* Token過期時間(單位:分鍾)。
*/
private int expiredTime = 30;

/**
* 混淆。
*/
private String secret = "c8e3n23ia0wgn458yqwafn934uf";

public int getExpiredTime() {
return expiredTime;
}

public void setExpiredTime(int expiredTime) {
this.expiredTime = expiredTime;
}

public String getSecret() {
return secret;
}

public void setSecret(String secret) {
this.secret = secret;
}
}
3)Token認證對象和加密算法配置類。

/**
* @author LEEMER
* Create Date: 2019-05-20
*/
@Component
public class BeanConfig {

private TokenConfigBean tokenConfigBean;

public BeanConfig(TokenConfigBean tokenConfigBean) {
this.tokenConfigBean = tokenConfigBean;
}

/**
* Token認證對象。
*/
@Autowired
@Bean
public JWTVerifier getJwtVerifier(Algorithm algorithm) {
return JWT.require(algorithm).build();
}

/**
* Token加密算法。
*/
@Bean
public Algorithm getAlgorithm() {
try {
return Algorithm.HMAC256(tokenConfigBean.getSecret());
} catch (UnsupportedEncodingException e) {
LOGGER.error("生成Token加法算法失敗!", e);
throw new RuntimeException(e);
}
}

}
4)自定義控制器請求token認證注解。

/**
* 使用在傳統控制器的方法上,進行登錄判斷,如果沒有登錄則返回登錄界面。
*
* @author LEEMER
* Create Date: 2019-05-20
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ControllerAuthCheck {
}
/**
* @author LEEMER
* Create Date: 2019-05-20
*/
@Aspect
@Component
public class ControllerAuthCheckAspect {

private static final Logger LOGGER = LoggerFactory
.getLogger(ControllerAuthCheckAspect.class);

private UrlConfigBean urlConfigBean;

/**
* Token服務。
*/
private TokenService tokenService;

@Autowired
public ControllerAuthCheckAspect(TokenService tokenService,
UrlConfigBean urlConfigBean) {
this.tokenService = tokenService;
this.urlConfigBean = urlConfigBean;
}

/**
* 定義切入點。
*
* 所有包含AuthCheck注解的方法均會被攔截。
*/
@Pointcut("@annotation(cn.blackbox.annotation.ControllerAuthCheck)")
private void checkAuth() {}

/**
* 環繞增強。
*
* 在調用相關系統模塊時,進行權限檢查,沒有相關權限則不進行目標方法的調用。
*
*/
@Around(value = "checkAuth() && @annotation(controllerAuthCheck) && args(token, ..)",
argNames = "joinPoint, controllerAuthCheck, token")
private Object checkAuth(ProceedingJoinPoint joinPoint,
ControllerAuthCheck controllerAuthCheck,
String token) {
if (!token.startsWith("Bearer ")) {
return "redirect:" + urlConfigBean.getLogin();
} else {
token = token.substring(7);
}

if (!tokenService.isValid(token)) {
return "redirect:" + urlConfigBean.getLogin();
}

try {
return joinPoint.proceed();
} catch (Throwable throwable) {
LOGGER.error("控制器權限控制切面調用目標方法失敗!", throwable);
throw new RuntimeException(throwable);
}
}
}
 

二、使用Token實現相關模塊登錄認證。
使用場景:請求后台數據的時候我們可以通過token值判斷用戶是否登錄。

1)在JS里面我們可以localStorage.getItem("token")獲取token值。如下:

function getLoginDetails() {
$.ajax({
url:"/api/v1/stage/land_details",
method:"GET",
dataType:"JSON",
headers:{
token:localStorage.getItem("token")
},
success : function (result) {

}
})
}
2)后台校驗token值,通過我們自定義@ControllerAuthCheck注解實現token校驗功能。

@RequestMapping("/land_details")
@ControllerAuthCheck // 檢驗token的注解,必須要獲取token參數才能有效校驗
public String toLandDetailsView(@RequestParam(value = "token", required = false, defaultValue = "") String token){
return "/land_details";
}
--------------------- 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM