JWT(JSON Web Token)是一個開放標準,用于在各方之間以JSON對象安全地傳輸信息。
1.JWT請求流程
- 用戶使用瀏覽器發(fā)送賬號和密碼
2.服務器使用私鑰創(chuàng)建一個JWT
3.服務器返回這個JWT給瀏覽器
4.瀏覽器將該JWT放在請求頭中向服務器發(fā)送請求
5.服務器驗證JWT
6.根據(jù)授權規(guī)則返回資源給瀏覽器
2.JWT的組成
JWT的格式為Header.Payload.Signature, 分為三個部分,header,payload,signature
在實際的應用中,JWT作為一個無狀態(tài)的授權校驗技術,非常適合分布式系統(tǒng)架構。服務器不需要存儲用戶狀態(tài),無需采用redis等技術來實現(xiàn)各個服務節(jié)點之間共享Session數(shù)據(jù)。
JWT的格式為:Header.Payload.Signature,即JWT包含3部分。為Header、payload、和signature。
header是通過Base64編碼生成的字符串,header中存放的內容說明編碼對象是一個JWT。
payload主要包含claim(斷言),claim是一些實體的狀態(tài)和額外的元數(shù)據(jù),有三種類型:Reserved、Public和Private.
Reserved 預定義的
Public claim 根據(jù)需要定義自己的字段
Private claim自定義字段,可以用來雙方之間交換信息負載。需要經(jīng)過Base64Url編碼后作為JWT的結構第二部分。
signature簽名
簽名需要使用編碼后的header和payload及一個密鑰,使用header中指定的簽名算法進行簽名。
流程:
1.將header和claim分別使用Base64編碼,生成字符串header和payload。
2.將header和payload字符串以header.payload格式組合在一起,形成一個字符串。
3.使用過上面定義好的加密算法和一個存放在服務器上用于進行驗證的密鑰來對這個字符串進行加密,形成一個新的字符串,這個字符串就是signature。
接下來使用一個小demo來記錄實際開發(fā)過程中的應用
3.下載地址
https://gitee.com/xgkp/yzjwt.git
4.配置安全類
JWT安全配置需要繼承WebSecurityConfigurerAdapter,然后重寫方法.
public class WebSecurityConfigJwt extends WebSecurityConfigurerAdapter
裝載BCryptPasswordEncoder密碼編碼器
@Bean
public PasswordEncoder passwordEncoder3() {
return BCryptPasswordEncoder();
}
常規(guī)配置
@Override
propected void configure(HttpSecurity http) {
}
注意這里的詳細配置
0.http.antMatcher("/jwt/**").
formLogin().usernameParameter("name").
passwordParameter("pwd").loginPage("/jwt/login").
successHander(jwtAuthenticationSuccesshandler).
failureHandler(jwtAuthenticatioFalHander)
這里指定登錄的控制器
.and().authorizeRequests()
認證拼接
1.antMatchers("/register/mobile").permitAll()
表示手機注冊這個接口的路徑,誰都可以訪問的
2.antMatchers("/article/\**\*").authenticated()
表示需要登陸之后才能訪問article/路徑下的資源
3.antMatchers("/jwt/tasks/**").hasRole("USER")
表示需要登錄且角色為USER的才能訪問jwt/tasks/路徑下的資源。
5.處理注冊
這里通過用戶名和手機號注冊:
1.先判斷用戶名是否被占用,再判斷手機號是否被占用,常規(guī)操作,不解釋。
2.接下來對密碼進行加密操作 ,使用BCryptPasswordEncoder來實現(xiàn)
String pass = "admin";
BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();
String hashPass = bcryptPasswordEncoder.encode(pass);
System.out.println(hashPass);
這個方法的強大之處在于,即使你每次輸入的密碼是同一個值,但是它的出的結果是不一樣的。而且雖然得出的結果不一樣,但是校驗還是能夠通過。
3.用戶信息角色綁定
List<Role> roles = new ArrayList<>();
UserRole role1 = userRoleRepository.findByRolename("ROLE_USER");
roles.add(role1);
user.setRoles(roles);
userRepository.save(user);
這里有兩個需要 注意的地方
1.如果數(shù)據(jù)庫role表里面沒有內容,那么以上role1值肯定null
2.如果role表里有n內容,但是沒有條數(shù)據(jù)滿足role name= ROLE_USER,那么role1的值還是為null
這兩種情況都會導致報錯
A granted authority textual representation is requred
表示某個方法的實參不能為null
我這里的解決方案是測試之前在role表里面添加一條數(shù)據(jù)
| id | cname | rolename |
|---|---|---|
| 11 | canmetest | ROLE_USER |
不要問我為什么這里是ROLE_USER,我這里也是猜的,而猜測的根據(jù)來源是在代碼中有個findRoleName("ROLE_USER")
6.處理登錄
同樣的邏輯處理 ,
1.先判斷這個用戶存不存在于數(shù)據(jù)庫中,這時候為了方便多種方式安全驗證登錄,使用一個Service類來實現(xiàn),簡單邏輯不解釋
@Service
public class JwtUserSecurityservice implements UseDetailServce
2.用戶存在性驗證完了以后需要驗證后續(xù)處理,創(chuàng)建token之類的操作
這個是在另一個類中實現(xiàn)
@Component("jtAuthentcaionSuccessHandler")
public class JwtAuthenticationSuccesssHandler
extends
SavedReuestAwareAuthenticationSuccesHandler{}
重寫一個方法
@Override
public void onAuthenticationSuccess
(HttpServletRequest httpServletRequest ,
HttpServletResponse httpServletResponse,
Authentication authentication)
throw IOException,ServletException {}
token值的生成和返回設置的實現(xiàn)思路
String token = JwtTokenUtils.createToken(user.getUsename(),role,true);
httpServletResponse.setHeader("token",JwtTokenUtils.TOKEN_PREFIX + token);
httpservletResponse.setContentType("application/json;charset=utf-8");
具體的參數(shù)實現(xiàn)細節(jié)
if (principal != null && principal instanceof UserDetails) {
UserDetails user = (UserDetails) principal;
httpServletRequest.getSession().setAttribute("userDetail", user);
String role = "";
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
for (GrantedAuthority authority : authorities){
role = authority.getAuthority();
}
//...此處省略上一步的核心代碼
}
登錄驗證失敗的時候,需要進行的后續(xù)處理,直接構造一個json類型的返回值返回即可
httpServletRequest.setCharacterEncoding("UTF-8");
// 獲得用戶名密碼
String username = httpServletRequest.getParameter("uname");
String password = httpServletRequest.getParameter("pwd");
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{\"status\":\"error\",\"message\":\"用戶名或密碼錯誤\"}");
out.flush();
out.close();
其它的報錯
PageRequest(int,int,org.springframework.data.domain.Sort) 在 org.springframework
這個說明Spring Boot中原來的構造方法過期了,使用新的寫法
Sort sort = Sort.by(Sort.Order.desc("create_date"));
Pageable pageable =
PageRequest.of(Integer.parseInt(page), Integer.parseInt(size), sort);
7.開始測試
1.注冊測試
post man 工具:
post方法
form-data
mobile:18201430259
name:wangchuang
password:123456

2.登錄測試
post 方法
form-data
name:wangchuang
password:123456


將圖示位置的token復制出來備用
3.權限操作測試
獲取任務列表
post請求
參數(shù)無
Headers
Authorization:上一步復制出來的token
Content Type:application/x-www-form-urlencoded
看圖7_4所示

這樣請求的結果有兩種情況
1.返回403或者500表示身份驗證失敗或者服務端的代碼報錯
2.返回200說明token的身份驗證成功了
8.其他
JWTAuthorizationFilter類中調用了getAuthentication方法,里面實現(xiàn)了一個通過原來的token新建一個新的token的方法
具體實現(xiàn)見如下代碼
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
String username = JwtTokenUtils.getUsername(token);
String role = JwtTokenUtils.getUserRole(token);
if (username != null){
return new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority(role))
);
}
總體來講看著書本磕磕絆絆的完成了jwt的實際使用,對整體的驗證登錄流程有個基本的認識。雖說處理各種版本和報錯提示有點兒坎坷,但結果還算是比較滿意的,關于權限和角色的細節(jié)和其它操作需求未來慢慢補充。
學習的過程還真是嚼一路辛苦,飲一路汗水啊。
朋友圈大概主題都是:來自上海的航班正在逐漸點亮小島~~
雖然有些抱怨,但是我們也知道閉關只能導致落后,舉個不恰當?shù)睦?,清政府閉關那么多年,最后不還是被堅船利炮給打開了國門[手動狗頭]
海南也有確診病例了,有小區(qū)被管控了,有居家辦公的前同事了,有同事路過封控區(qū)要求主動去做核酸了。期待疫情早日結束,還世界一份太平?。?!
2022年04月01日 ???復興城