再見Spring Security!推薦一款功能強大的權(quán)限認證框架,用起來夠優(yōu)雅!
在我們做SpringBoot項目的時候,認證授權(quán)是必不可少的功能!我們經(jīng)常會選擇Shiro、Spring Security這類權(quán)限認證框架來實現(xiàn),但這些框架使用起來有點繁瑣,而且功能也不夠強大。最近發(fā)現(xiàn)一款功能強大的權(quán)限認證框架Sa-Token,它使用簡單、API設計優(yōu)雅,推薦給大家!
Sa-Token簡介

使用
在SpringBoot中使用Sa-Token是非常簡單的,接下來我們使用它來實現(xiàn)最常用的認證授權(quán)功能,包括登錄認證、角色認證和權(quán)限認證。
集成及配置
Sa-Token的集成和配置都非常簡單,不愧為開箱即用。
首先我們需要在項目的 pom.xml中添加Sa-Token的相關依賴;
<!-- Sa-Token 權(quán)限認證 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.24.0</version>
</dependency>
然后在 application.yml中添加Sa-Token的相關配置,考慮到要支持前后端分離項目,我們關閉從cookie中讀取token,改為從head中讀取token。
# Sa-Token配置
sa-token:
# token名稱 (同時也是cookie名稱)
token-name: Authorization
# token有效期,單位秒,-1代表永不過期
timeout: 2592000
# token臨時有效期 (指定時間內(nèi)無操作就視為token過期),單位秒
activity-timeout: -1
# 是否允許同一賬號并發(fā)登錄 (為false時新登錄擠掉舊登錄)
is-concurrent: true
# 在多人登錄同一賬號時,是否共用一個token (為false時每次登錄新建一個token)
is-share: false
# token風格
token-style: uuid
# 是否輸出操作日志
is-log: false
# 是否從cookie中讀取token
is-read-cookie: false
# 是否從head中讀取token
is-read-head: true
登錄認證
在管理系統(tǒng)中,除了登錄接口,基本都需要登錄認證,在Sa-Token中使用路由攔截鑒權(quán)是最方便的,下面我們來實現(xiàn)下。
實現(xiàn)登錄認證非常簡單,首先在 UmsAdminController中添加一個登錄接口;
/**
* 后臺用戶管理
* Created by macro on 2018/4/26.
*/
@Controller
@Api(tags = "UmsAdminController", description = "后臺用戶管理")
@RequestMapping("/admin")
public class UmsAdminController {
@Autowired
private UmsAdminService adminService;
@ApiOperation(value = "登錄以后返回token")
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public CommonResult login(@RequestParam String username, @RequestParam String password) {
SaTokenInfo saTokenInfo = adminService.login(username, password);
if (saTokenInfo == null) {
return CommonResult.validateFailed("用戶名或密碼錯誤");
}
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put("token", saTokenInfo.getTokenValue());
tokenMap.put("tokenHead", saTokenInfo.getTokenName());
return CommonResult.success(tokenMap);
}
}
然后在 UmsAdminServiceImpl添加登錄的具體邏輯,先驗證密碼,然后調(diào)用StpUtil.login(adminUser.getId())即可實現(xiàn)登錄,調(diào)用API一行搞定;
/**
* Created by macro on 2020/10/15.
*/
@Slf4j
@Service
public class UmsAdminServiceImpl implements UmsAdminService {
@Override
public SaTokenInfo login(String username, String password) {
SaTokenInfo saTokenInfo = null;
AdminUser adminUser = getAdminByUsername(username);
if (adminUser == null) {
return null;
}
if (!SaSecureUtil.md5(password).equals(adminUser.getPassword())) {
return null;
}
// 密碼校驗成功后登錄,一行代碼實現(xiàn)登錄
StpUtil.login(adminUser.getId());
// 獲取當前登錄用戶Token信息
saTokenInfo = StpUtil.getTokenInfo();
return saTokenInfo;
}
}
我們再添加一個測試接口用于查詢當前登錄狀態(tài),返回 true表示已經(jīng)登錄;
/**
* Created by macro on 2020/10/15.
*/
@Slf4j
@Service
public class UmsAdminServiceImpl implements UmsAdminService {
@ApiOperation(value = "查詢當前登錄狀態(tài)")
@RequestMapping(value = "/isLogin", method = RequestMethod.GET)
@ResponseBody
public CommonResult isLogin() {
return CommonResult.success(StpUtil.isLogin());
}
}
之后可以通過Swagger訪問登錄接口來獲取Token了,使用賬號為 admin:123456,訪問地址:http://localhost:8088/swagger-ui/

然后在 Authorization請求頭中添加獲取到的token;

訪問 /admin/isLogin接口,data屬性就會返回true了,表示你已經(jīng)是登錄狀態(tài)了;

接下來我們需要把除登錄接口以外的接口都添加登錄認證,添加Sa-Token的Java配置類 SaTokenConfig,注冊一個路由攔截器SaRouteInterceptor,這里我們的IgnoreUrlsConfig配置會從配置文件中讀取白名單配置;
/**
* Sa-Token相關配置
*/
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
/**
* 注冊sa-token攔截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaRouteInterceptor((req, resp, handler) -> {
// 獲取配置文件中的白名單路徑
List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
// 登錄認證:除白名單路徑外均需要登錄認證
SaRouter.match(Collections.singletonList("/**"), ignoreUrls, StpUtil::checkLogin);
})).addPathPatterns("/**");
}
}
application.yml文件中的白名單配置如下,注意開放Swagger的訪問路徑和靜態(tài)資源路徑;
# 訪問白名單路徑
secure:
ignored:
urls:
- /
- /swagger-ui/
- /*.html
- /favicon.ico
- /**/*.html
- /**/*.css
- /**/*.js
- /swagger-resources/**
- /v2/api-docs/**
- /actuator/**
- /admin/login
- /admin/isLogin
由于未登錄狀態(tài)下訪問接口,Sa-Token會拋出 NotLoginException異常,所以我們需要全局處理下;
/**
* 全局異常處理
* Created by macro on 2020/2/27.
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理未登錄的異常
*/
@ResponseBody
@ExceptionHandler(value = NotLoginException.class)
public CommonResult handleNotLoginException(NotLoginException e) {
return CommonResult.unauthorized(e.getMessage());
}
}
之后當我們在登錄狀態(tài)下訪問接口時,可以獲取到數(shù)據(jù);

當我們未登錄狀態(tài)(不帶token)時無法正常訪問接口,返回 code為401。

角色認證
角色認證也就是我們定義好一套規(guī)則,比如 ROLE-ADMIN角色可以訪問/brand下的所有資源,而ROLE_USER角色只能訪問/brand/listAll,接下來我們來實現(xiàn)下角色認證。
首先我們需要擴展Sa-Token的 StpInterface接口,通過實現(xiàn)方法來返回用戶的角色碼和權(quán)限碼;
/**
* 自定義權(quán)限驗證接口擴展
*/
@Component
public class StpInterfaceImpl implements StpInterface {
@Autowired
private UmsAdminService adminService;
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
AdminUser adminUser = adminService.getAdminById(Convert.toLong(loginId));
return adminUser.getRole().getPermissionList();
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
AdminUser adminUser = adminService.getAdminById(Convert.toLong(loginId));
return Collections.singletonList(adminUser.getRole().getName());
}
}
然后在Sa-Token的攔截器中配置路由規(guī)則, ROLE_ADMIN角色可以訪問所有路徑,而ROLE_USER只能訪問/brand/listAll路徑;
/**
* Sa-Token相關配置
*/
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
/**
* 注冊sa-token攔截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaRouteInterceptor((req, resp, handler) -> {
// 獲取配置文件中的白名單路徑
List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
// 登錄認證:除白名單路徑外均需要登錄認證
SaRouter.match(Collections.singletonList("/**"), ignoreUrls, StpUtil::checkLogin);
// 角色認證:ROLE_ADMIN可以訪問所有接口,ROLE_USER只能訪問查詢?nèi)拷涌?/span>
SaRouter.match("/brand/listAll", () -> {
StpUtil.checkRoleOr("ROLE_ADMIN","ROLE_USER");
//強制退出匹配鏈
SaRouter.stop();
});
SaRouter.match("/brand/**", () -> StpUtil.checkRole("ROLE_ADMIN"));
})).addPathPatterns("/**");
}
}
當用戶不是被允許的角色訪問時,Sa-Token會拋出 NotRoleException異常,我們可以全局處理下;
/**
* 全局異常處理
* Created by macro on 2020/2/27.
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理沒有角色的異常
*/
@ResponseBody
@ExceptionHandler(value = NotRoleException.class)
public CommonResult handleNotRoleException(NotRoleException e) {
return CommonResult.forbidden(e.getMessage());
}
}
我們現(xiàn)在有兩個用戶, admin用戶具有ROLE_ADMIN角色,macro用戶具有ROLE_USER角色;

使用 admin賬號訪問/brand/list接口可以正常訪問;

使用 macro賬號訪問/brand/list接口無法正常訪問,返回code為403。

權(quán)限認證
當我們給角色分配好權(quán)限,然后給用戶分配好角色后,用戶就擁有了這些權(quán)限。我們可以為每個接口分配不同的權(quán)限,擁有該權(quán)限的用戶就可以訪問該接口。這就是權(quán)限認證,接下來我們來實現(xiàn)下它。
我們可以在Sa-Token的攔截器中配置路由規(guī)則, admin用戶可以訪問所有路徑,而macro用戶只有讀取的權(quán)限,沒有寫、改、刪的權(quán)限;
/**
* Sa-Token相關配置
*/
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
/**
* 注冊sa-token攔截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaRouteInterceptor((req, resp, handler) -> {
// 獲取配置文件中的白名單路徑
List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
// 登錄認證:除白名單路徑外均需要登錄認證
SaRouter.match(Collections.singletonList("/**"), ignoreUrls, StpUtil::checkLogin);
// 權(quán)限認證:不同接口, 校驗不同權(quán)限
SaRouter.match("/brand/listAll", () -> StpUtil.checkPermission("brand:read"));
SaRouter.match("/brand/create", () -> StpUtil.checkPermission("brand:create"));
SaRouter.match("/brand/update/{id}", () -> StpUtil.checkPermission("brand:update"));
SaRouter.match("/brand/delete/{id}", () -> StpUtil.checkPermission("brand:delete"));
SaRouter.match("/brand/list", () -> StpUtil.checkPermission("brand:read"));
SaRouter.match("/brand/{id}", () -> StpUtil.checkPermission("brand:read"));
})).addPathPatterns("/**");
}
}
當用戶無權(quán)限訪問時,Sa-Token會拋出 NotPermissionException異常,我們可以全局處理下;
/**
* 全局異常處理
* Created by macro on 2020/2/27.
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理沒有權(quán)限的異常
*/
@ResponseBody
@ExceptionHandler(value = NotPermissionException.class)
public CommonResult handleNotPermissionException(NotPermissionException e) {
return CommonResult.forbidden(e.getMessage());
}
}
使用 admin賬號訪問/brand/delete接口可以正常訪問;

使用 macro賬號訪問/brand/delete無法正常訪問,返回code為403。

總結(jié)
參考資料
Sa-Token的官方文檔很全,也很良心,不僅提供了解決方式,還提供了解決思路,強烈建議大家去看下。

有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關注Java之道公眾號
好文章,我在看??
評論
圖片
表情
