本篇文章將教你們在 shiro + springBoot 的基礎上整合 JWT (JSON Web Token) 若是對 shiro 如何整合 springBoot 還不瞭解的能夠先去看個人上一篇文章 《教你 Shiro 整合 SpringBoot,避開各類坑》git
附上源碼:github.com/HowieYuan/s…github
JSON Web Token(JWT)是一個很是輕巧的規範。這個規範容許咱們使用 JWT 在用戶和服務器之間傳遞安全可靠的信息。算法
咱們利用必定的編碼生成 Token,並在 Token 中加入一些非敏感信息,將其傳遞。spring
一個完整的 Token : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
數據庫
在本項目中,咱們規定每次請求時,須要在請求頭中帶上 token ,經過 token 檢驗權限,如沒有,則說明當前爲遊客狀態(或者是登錄 login 接口等)跨域
咱們利用 JWT 的工具類來生成咱們的 token,這個工具類主要有生成 token 和 校驗 token 兩個方法安全
生成 token 時,指定 token 過時時間 EXPIRE_TIME
和簽名密鑰 SECRET
,而後將 date 和 username 寫入 token 中,並使用帶有密鑰的 HS256 簽名算法進行簽名bash
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWT.create()
.withClaim("username", username)
//到期時間
.withExpiresAt(date)
//建立一個新的JWT,並使用給定的算法進行標記
.sign(algorithm);
複製代碼
每一個用戶有對應的角色(user,admin),權限(normal,vip),而 user 角色默認權限爲 normal, admin 角色默認權限爲 vip(固然,user 也能夠是 vip)服務器
在上一篇文章中,咱們使用的是 shiro 默認的權限攔截 Filter,而由於 JWT 的整合,咱們須要自定義本身的過濾器 JWTFilter,JWTFilter 繼承了 BasicHttpAuthenticationFilter,並部分原方法進行了重寫app
該過濾器主要有三步:
((HttpServletRequest) request).getHeader("Token") != null
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
//判斷請求的請求頭是否帶上 "Token"
if (((HttpServletRequest) request).getHeader("Token") != null) {
//若是存在,則進入 executeLogin 方法執行登入,檢查 token 是否正確
try {
executeLogin(request, response);
return true;
} catch (Exception e) {
//token 錯誤
responseError(response, e.getMessage());
}
}
//若是請求頭不存在 Token,則多是執行登錄操做或者是遊客狀態訪問,無需檢查 token,直接返回 true
return true;
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("Token");
JWTToken jwtToken = new JWTToken(token);
// 提交給realm進行登入,若是錯誤他會拋出異常並被捕獲
getSubject(request, response).login(jwtToken);
// 若是沒有拋出異常則表明登入成功,返回true
return true;
}
複製代碼
/unauthorized/**
另外,我將跨域支持放到了該過濾器來處理
依然是咱們的自定義 Realm ,對這一塊還不瞭解的能夠先看個人上一篇 shiro 的文章
if (username == null || !JWTUtil.verify(token, username)) {
throw new AuthenticationException("token認證失敗!");
}
String password = userMapper.getPassword(username);
if (password == null) {
throw new AuthenticationException("該用戶不存在!");
}
int ban = userMapper.checkUserBanStatus(username);
if (ban == 1) {
throw new AuthenticationException("該用戶已被封號!");
}
複製代碼
拿到傳來的 token ,檢查 token 是否有效,用戶是否存在,以及用戶的封號狀況
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//得到該用戶角色
String role = userMapper.getRole(username);
//每一個角色擁有默認的權限
String rolePermission = userMapper.getRolePermission(username);
//每一個用戶能夠設置新的權限
String permission = userMapper.getPermission(username);
Set<String> roleSet = new HashSet<>();
Set<String> permissionSet = new HashSet<>();
//須要將 role, permission 封裝到 Set 做爲 info.setRoles(), info.setStringPermissions() 的參數
roleSet.add(role);
permissionSet.add(rolePermission);
permissionSet.add(permission);
//設置該用戶擁有的角色和權限
info.setRoles(roleSet);
info.setStringPermissions(permissionSet);
複製代碼
利用 token 中得到的 username,分別從數據庫查到該用戶所擁有的角色,權限,存入 SimpleAuthorizationInfo 中
設置好咱們自定義的 filter,並使全部請求經過咱們的過濾器,除了咱們用於處理未認證請求的 /unauthorized/**
@Bean
public ShiroFilterFactoryBean factory(SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 添加本身的過濾器而且取名爲jwt
Map<String, Filter> filterMap = new HashMap<>();
//設置咱們自定義的JWT過濾器
filterMap.put("jwt", new JWTFilter());
factoryBean.setFilters(filterMap);
factoryBean.setSecurityManager(securityManager);
Map<String, String> filterRuleMap = new HashMap<>();
// 全部請求經過咱們本身的JWT Filter
filterRuleMap.put("/**", "jwt");
// 訪問 /unauthorized/** 不經過JWTFilter
filterRuleMap.put("/unauthorized/**", "anon");
factoryBean.setFilterChainDefinitionMap(filterRuleMap);
return factoryBean;
}
複製代碼
這兩個註解爲咱們主要的權限控制註解, 如
// 擁有 admin 角色能夠訪問
@RequiresRoles("admin")
複製代碼
// 擁有 user 或 admin 角色能夠訪問
@RequiresRoles(logical = Logical.OR, value = {"user", "admin"})
複製代碼
// 擁有 vip 和 normal 權限能夠訪問
@RequiresPermissions(logical = Logical.AND, value = {"vip", "normal"})
複製代碼
// 擁有 user 或 admin 角色,且擁有 vip 權限能夠訪問
@GetMapping("/getVipMessage")
@RequiresRoles(logical = Logical.OR, value = {"user", "admin"})
@RequiresPermissions("vip")
public ResultMap getVipMessage() {
return resultMap.success().code(200).message("成功得到 vip 信息!");
}
複製代碼
當咱們寫的接口擁有以上的註解時,若是請求沒有帶有 token 或者帶了 token 但權限認證不經過,則會報 UnauthenticatedException 異常,可是我在 ExceptionController 類對這些異常進行了集中處理
@ExceptionHandler(ShiroException.class)
public ResultMap handle401() {
return resultMap.fail().code(401).message("您沒有權限訪問!");
}
複製代碼
這時,出現 shiro 相關的異常時則會返回
{
"result": "fail",
"code": 401,
"message": "您沒有權限訪問!"
}
複製代碼
除了以上兩種,還有 @RequiresAuthentication ,@RequiresUser 等註解
用戶角色分爲三類,管理員 admin,普通用戶 user,遊客 guest;admin 默認權限爲 vip,user 默認權限爲 normal,當 user 升級爲 vip 權限時能夠訪問 vip 權限的頁面。
具體實現能夠看源代碼(開頭已經給出地址)
登錄接口不帶有 token,當登錄密碼,用戶名驗證正確後返回 token。
@PostMapping("/login")
public ResultMap login(@RequestParam("username") String username,
@RequestParam("password") String password) {
String realPassword = userMapper.getPassword(username);
if (realPassword == null) {
return resultMap.fail().code(401).message("用戶名錯誤");
} else if (!realPassword.equals(password)) {
return resultMap.fail().code(401).message("密碼錯誤");
} else {
return resultMap.success().code(200).message(JWTUtil.createToken(username));
}
}
複製代碼
{
"result": "success",
"code": 200,
"message": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MjUxODQyMzUsInVzZXJuYW1lIjoiaG93aWUifQ.fG5Qs739Hxy_JjTdSIx_iiwaBD43aKFQMchx9fjaCRo"
}
複製代碼
// 捕捉shiro的異常
@ExceptionHandler(ShiroException.class)
public ResultMap handle401() {
return resultMap.fail().code(401).message("您沒有權限訪問!");
}
// 捕捉其餘全部異常
@ExceptionHandler(Exception.class)
public ResultMap globalException(HttpServletRequest request, Throwable ex) {
return resultMap.fail()
.code(getStatus(request).value())
.message("訪問出錯,沒法訪問: " + ex.getMessage());
}
複製代碼
UserController(user 或 admin 能夠訪問) 在接口上帶上 @RequiresRoles(logical = Logical.OR, value = {"user", "admin"})
@RequiresPermissions("vip")
AdminController(admin 能夠訪問) 在接口上帶上 @RequiresRoles("admin")
GuestController(全部人能夠訪問) 不作權限處理