你好我是辰兮,很高興你能來閱讀,本篇給你介紹JWT單點登錄的代碼實現,後續會進一步分享源碼的學習,獻給初學者,共同成長,家一起進步。
一、SSO概念
單點登錄(Single Sign On),簡稱爲 SSO,是目前比較流行的企業業務整合的解決方案之一。SSO的定義是在多個應用系統中,用戶只需要登錄一次就可以訪問所有相互信任的應用系統。
阿里系的淘寶和天貓,很明顯地我們可以知道這是兩個系統,但是你在使用的時候,登錄了天貓,淘寶也會自動登錄。
簡述:當你成功登錄後,系統會返回你一個令牌(憑證),你可以拿着這個令牌去訪問所有相關的系統,只要你令牌即可訪問,沒有令牌就被攔截不能訪問。(參考下圖)
1、相比於單系統登錄,sso需要一個獨立的認證中心,只有認證中心能接受用戶的用戶名密碼等安全信息,其他系統不提供登錄入口,只接受認證中心的間接授權。
2、間接授權通過令牌實現,sso認證中心驗證用戶的用戶名密碼沒問題,創建授權令牌,在接下來的跳轉過程中,授權令牌作爲參數發送給各個子系統,子系統拿到令牌,即得到了授權,可以藉此創建局部會話,局部會話登錄方式與單系統的登錄方式相同。
二、JWT單點登錄步驟
添加依賴
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
controller層的實現
@RequestMapping("/login")
public Map<String,String> login(String userName, String password){
Map<String,String> map=new HashMap<>();
try{
String token = Jwts.builder().setSubject(userName) //主題,可以放用戶的詳細信息
.setIssuedAt(new Date()) //token創建時間
.setExpiration(new Date(System.currentTimeMillis() + 60000)) //token過期時間
.setId("userId") //用戶ID
//.setClaims(hashMap) //配置角色信息
.signWith(SignatureAlgorithm.HS256, "WuHan") //加密方式和加密密碼
.compact();
// System.out.println("token:"+token);
map.put("code","1");
map.put("msg","success");
map.put("token",token);
map.put("user",userName);
}catch (Exception e){
map.put("code","0");
map.put("msg","fail");
e.printStackTrace();
}
return map;
}
登錄後可以把token打印出來自己看看,測試如下
token:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4YyIsImlhdCI6MTU5MTE1NDQwNiwiZXhwIjoxNTkxMTU0NDY2LCJqdGkiOiJ1c2VySWQifQ.c7gCDgIQ_I40dIWRxyG4yd1xaZQyWflnC7kX2Uoc9H8
token:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4YyIsImlhdCI6MTU5MTE1OTA1MSwiZXhwIjoxNTkxMTU5MTExLCJqdGkiOiJ1c2VySWQifQ.eUp0O2y83dc14f9NPUX049E9VxMpnkjuTcrheq2r9fM
token構成包含三個部分:
- Header 頭部
- Payload 負載
- Signature 簽名
注意事項:
①根據需求設計過期時間(1秒 =1000 毫秒 )
②因爲我把時間設計到token裏面了所以你可以測試每次產生的token不一樣
③設置userId那一欄-我目前是寫死了 setId(“userId”) ,你可以根據需求設計不同的值進去
校驗Token
try {
JwtParser parser = Jwts.parser();
parser.setSigningKey("WuHan");//解析 要和上面“暗號”一樣
Jws<Claims> claimsJws = parser.parseClaimsJws(token);
Claims body = claimsJws.getBody();
String username = body.getSubject();
// Object role = body.get("role");
return true;
} catch (ExpiredJwtException e) {
e.printStackTrace();
} catch (UnsupportedJwtException e) {
e.printStackTrace();
} catch (MalformedJwtException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
setSigningKey() 與builder中籤名方法signWith()對應,parser中的此方法擁有與signWith()方法相同的三種參數形式,用於設置JWT的簽名key,用戶後面對JWT進行解析。
isSigned() 校驗JWT是否進行簽名。方法很簡單,以分隔符" . ",截取JWT第三段,即簽名部分進行判斷。
- 案例前端代碼實現
登錄模塊如果成功登錄,則存放token(也可以存放user根據需求看)
<script type="text/javascript">
$("#login").click(function () {
var name=$("#userName").val();
var pwd=$("#password").val();
$.post("http://localhost:8080/user/login",{userName:name,password:pwd},function(data) {
console.log(data);
if(data.code=="1"){
/* document.cookie=data.token;*/
sessionStorage.setItem("token",data.token);
sessionStorage.setItem("user",data.user);
window.location.href="index.html";
}else{
window.location.href="login.html";
}
},"json");
});
</script>
- 後續相關頁面的安全校驗,如果需要token才能訪問的頁面
從存儲的地方取出token,來進行校驗,沒有token說明沒有登錄則返回登錄界面
<script>
var data = sessionStorage.getItem("token");
if(data==null){
window.location.href="login.html";
}
</script>
補充:sessionStorage和localStorage
代碼 | 含義 |
---|---|
window.sessionStorage(會話存儲) | 暫時儲存,瀏覽器關閉之後會清除 |
window.localStorage (本地存儲) | 本地儲存,瀏覽器關閉之後依舊不會清除,只能人爲刪除 |
平時儲存的話建議使用sessionStorage;
開啓Springboot攔截器引用上述校驗token的代碼
/**
* 登錄攔截
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getParameter("token");
try {
JwtParser parser = Jwts.parser();
parser.setSigningKey("WuHan");
Jws<Claims> claimsJws = parser.parseClaimsJws(token);
Claims body = claimsJws.getBody();
String username = body.getSubject();
Object role = body.get("role");
return true;
} catch (ExpiredJwtException e) {
e.printStackTrace();
} catch (UnsupportedJwtException e) {
e.printStackTrace();
} catch (MalformedJwtException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
加載攔截器
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Resource
private LoginInterceptor loginInterceptor;
/**
* 添加攔截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//.execudePathPatterns()//可以添加不攔截的地址
registry.addInterceptor(loginInterceptor).addPathPatterns("/**");
}
}
The best investment is to invest in yourself
2020.06.03 記錄辰兮的第75篇博客