当前位置:   article > 正文

再见Spring Security!推荐一款功能强大的权限认证框架,用起来够优雅!

有哪些springsecurity的替代产品

‍在我们做SpringBoot项目的时候,认证授权是必不可少的功能!我们经常会选择Shiro、Spring Security这类权限认证框架来实现,但这些框架使用起来有点繁琐,而且功能也不够强大。最近发现一款功能强大的权限认证框架Sa-Token,它使用简单、API设计优雅,推荐给大家!

 

Sa-Token简介

Sa-Token是一款轻量级的Java权限认证框架,可以用来解决登录认证、权限认证、Session会话、单点登录、OAuth2.0、微服务网关鉴权等一系列权限相关问题。

框架集成简单、开箱即用、API设计优雅,通过Sa-Token,你将以一种极其简单的方式实现系统的权限认证部分,有时候往往只需一行代码就能实现功能。

Sa-Token功能很全,具体可以参考下图。

 

使用

在SpringBoot中使用Sa-Token是非常简单的,接下来我们使用它来实现最常用的认证授权功能,包括登录认证、角色认证和权限认证。

集成及配置

Sa-Token的集成和配置都非常简单,不愧为开箱即用。

  • 首先我们需要在项目的pom.xml中添加Sa-Token的相关依赖;

  1. <!-- Sa-Token 权限认证 -->
  2. <dependency>
  3.     <groupId>cn.dev33</groupId>
  4.     <artifactId>sa-token-spring-boot-starter</artifactId>
  5.     <version>1.24.0</version>
  6. </dependency>
  • 然后在application.yml中添加Sa-Token的相关配置,考虑到要支持前后端分离项目,我们关闭从cookie中读取token,改为从head中读取token。

  1. # Sa-Token配置
  2. sa-token:
  3.   # token名称 (同时也是cookie名称)
  4.   token-name: Authorization
  5.   # token有效期,单位秒,-1代表永不过期
  6.   timeout: 2592000
  7.   # token临时有效期 (指定时间内无操作就视为token过期),单位秒
  8.   activity-timeout: -1
  9.   # 是否允许同一账号并发登录 (为false时新登录挤掉旧登录)
  10.   is-concurrent: true
  11.   # 在多人登录同一账号时,是否共用一个token (为false时每次登录新建一个token)
  12.   is-share: false
  13.   # token风格
  14.   token-style: uuid
  15.   # 是否输出操作日志
  16.   is-log: false
  17.   # 是否从cookie中读取token
  18.   is-read-cookie: false
  19.   # 是否从head中读取token
  20.   is-read-head: true

登录认证

在管理系统中,除了登录接口,基本都需要登录认证,在Sa-Token中使用路由拦截鉴权是最方便的,下面我们来实现下。

  • 实现登录认证非常简单,首先在UmsAdminController中添加一个登录接口;

  1. /**
  2.  * 后台用户管理
  3.  * Created by macro on 2018/4/26.
  4.  */
  5. @Controller
  6. @Api(tags = "UmsAdminController", description = "后台用户管理")
  7. @RequestMapping("/admin")
  8. public class UmsAdminController {
  9.     @Autowired
  10.     private UmsAdminService adminService;
  11.     @ApiOperation(value = "登录以后返回token")
  12.     @RequestMapping(value = "/login", method = RequestMethod.POST)
  13.     @ResponseBody
  14.     public CommonResult login(@RequestParam String username, @RequestParam String password) {
  15.         SaTokenInfo saTokenInfo = adminService.login(username, password);
  16.         if (saTokenInfo == null) {
  17.             return CommonResult.validateFailed("用户名或密码错误");
  18.         }
  19.         Map<String, String> tokenMap = new HashMap<>();
  20.         tokenMap.put("token", saTokenInfo.getTokenValue());
  21.         tokenMap.put("tokenHead", saTokenInfo.getTokenName());
  22.         return CommonResult.success(tokenMap);
  23.     }
  24. }
  • 然后在UmsAdminServiceImpl添加登录的具体逻辑,先验证密码,然后调用StpUtil.login(adminUser.getId())即可实现登录,调用API一行搞定;

  1. /**
  2.  * Created by macro on 2020/10/15.
  3.  */
  4. @Slf4j
  5. @Service
  6. public class UmsAdminServiceImpl implements UmsAdminService {
  7.     @Override
  8.     public SaTokenInfo login(String username, String password) {
  9.         SaTokenInfo saTokenInfo = null;
  10.         AdminUser adminUser = getAdminByUsername(username);
  11.         if (adminUser == null) {
  12.             return null;
  13.         }
  14.         if (!SaSecureUtil.md5(password).equals(adminUser.getPassword())) {
  15.             return null;
  16.         }
  17.         // 密码校验成功后登录,一行代码实现登录
  18.         StpUtil.login(adminUser.getId());
  19.         // 获取当前登录用户Token信息
  20.         saTokenInfo = StpUtil.getTokenInfo();
  21.         return saTokenInfo;
  22.     }
  23. }
  • 我们再添加一个测试接口用于查询当前登录状态,返回true表示已经登录;

  1. /**
  2.  * Created by macro on 2020/10/15.
  3.  */
  4. @Slf4j
  5. @Service
  6. public class UmsAdminServiceImpl implements UmsAdminService {
  7.     @ApiOperation(value = "查询当前登录状态")
  8.     @RequestMapping(value = "/isLogin", method = RequestMethod.GET)
  9.     @ResponseBody
  10.     public CommonResult isLogin() {
  11.         return CommonResult.success(StpUtil.isLogin());
  12.     }
  13. }
  • 之后可以通过Swagger访问登录接口来获取Token了,使用账号为admin:123456,访问地址:http://localhost:8088/swagger-ui/

  • 然后在Authorization请求头中添加获取到的token;

  • 访问/admin/isLogin接口,data属性就会返回true了,表示你已经是登录状态了;

  • 接下来我们需要把除登录接口以外的接口都添加登录认证,添加Sa-Token的Java配置类SaTokenConfig,注册一个路由拦截器SaRouteInterceptor,这里我们的IgnoreUrlsConfig配置会从配置文件中读取白名单配置;

  1. /**
  2.  * Sa-Token相关配置
  3.  */
  4. @Configuration
  5. public class SaTokenConfig implements WebMvcConfigurer {
  6.     @Autowired
  7.     private IgnoreUrlsConfig ignoreUrlsConfig;
  8.     /**
  9.      * 注册sa-token拦截器
  10.      */
  11.     @Override
  12.     public void addInterceptors(InterceptorRegistry registry) {
  13.         registry.addInterceptor(new SaRouteInterceptor((req, resp, handler) -> {
  14.             // 获取配置文件中的白名单路径
  15.             List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
  16.             // 登录认证:除白名单路径外均需要登录认证
  17.             SaRouter.match(Collections.singletonList("/**"), ignoreUrls, StpUtil::checkLogin);
  18.         })).addPathPatterns("/**");
  19.     }
  20. }
  • application.yml文件中的白名单配置如下,注意开放Swagger的访问路径和静态资源路径;

  1. # 访问白名单路径
  2. secure:
  3.   ignored:
  4.     urls:
  5.       - /
  6.       - /swagger-ui/
  7.       - /*.html
  8.       - /favicon.ico
  9.       - /**/*.html
  10.       - /**/*.css
  11.       - /**/*.js
  12.       - /swagger-resources/**
  13.       - /v2/api-docs/**
  14.       - /actuator/**
  15.       - /admin/login
  16.       - /admin/isLogin
  • 由于未登录状态下访问接口,Sa-Token会抛出NotLoginException异常,所以我们需要全局处理下;

  1. /**
  2.  * 全局异常处理
  3.  * Created by macro on 2020/2/27.
  4.  */
  5. @ControllerAdvice
  6. public class GlobalExceptionHandler {
  7.     /**
  8.      * 处理未登录的异常
  9.      */
  10.     @ResponseBody
  11.     @ExceptionHandler(value = NotLoginException.class)
  12.     public CommonResult handleNotLoginException(NotLoginException e) {
  13.         return CommonResult.unauthorized(e.getMessage());
  14.     }
  15. }
  • 之后当我们在登录状态下访问接口时,可以获取到数据;

  • 当我们未登录状态(不带token)时无法正常访问接口,返回code401

角色认证

角色认证也就是我们定义好一套规则,比如ROLE-ADMIN角色可以访问/brand下的所有资源,而ROLE_USER角色只能访问/brand/listAll,接下来我们来实现下角色认证。

  • 首先我们需要扩展Sa-Token的StpInterface接口,通过实现方法来返回用户的角色码和权限码;

  1. /**
  2.  * 自定义权限验证接口扩展
  3.  */
  4. @Component
  5. public class StpInterfaceImpl implements StpInterface {
  6.     @Autowired
  7.     private UmsAdminService adminService;
  8.     @Override
  9.     public List<String> getPermissionList(Object loginId, String loginType) {
  10.         AdminUser adminUser = adminService.getAdminById(Convert.toLong(loginId));
  11.         return adminUser.getRole().getPermissionList();
  12.     }
  13.     @Override
  14.     public List<String> getRoleList(Object loginId, String loginType) {
  15.         AdminUser adminUser = adminService.getAdminById(Convert.toLong(loginId));
  16.         return Collections.singletonList(adminUser.getRole().getName());
  17.     }
  18. }
  • 然后在Sa-Token的拦截器中配置路由规则,ROLE_ADMIN角色可以访问所有路径,而ROLE_USER只能访问/brand/listAll路径;

  1. /**
  2.  * Sa-Token相关配置
  3.  */
  4. @Configuration
  5. public class SaTokenConfig implements WebMvcConfigurer {
  6.     @Autowired
  7.     private IgnoreUrlsConfig ignoreUrlsConfig;
  8.     /**
  9.      * 注册sa-token拦截器
  10.      */
  11.     @Override
  12.     public void addInterceptors(InterceptorRegistry registry) {
  13.         registry.addInterceptor(new SaRouteInterceptor((req, resp, handler) -> {
  14.             // 获取配置文件中的白名单路径
  15.             List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
  16.             // 登录认证:除白名单路径外均需要登录认证
  17.             SaRouter.match(Collections.singletonList("/**"), ignoreUrls, StpUtil::checkLogin);
  18.             // 角色认证:ROLE_ADMIN可以访问所有接口,ROLE_USER只能访问查询全部接口
  19.             SaRouter.match("/brand/listAll", () -> {
  20.                 StpUtil.checkRoleOr("ROLE_ADMIN","ROLE_USER");
  21.                 //强制退出匹配链
  22.                 SaRouter.stop();
  23.             });
  24.             SaRouter.match("/brand/**", () -> StpUtil.checkRole("ROLE_ADMIN"));
  25.         })).addPathPatterns("/**");
  26.     }
  27. }
  • 当用户不是被允许的角色访问时,Sa-Token会抛出NotRoleException异常,我们可以全局处理下;

  1. /**
  2.  * 全局异常处理
  3.  * Created by macro on 2020/2/27.
  4.  */
  5. @ControllerAdvice
  6. public class GlobalExceptionHandler {
  7.     /**
  8.      * 处理没有角色的异常
  9.      */
  10.     @ResponseBody
  11.     @ExceptionHandler(value = NotRoleException.class)
  12.     public CommonResult handleNotRoleException(NotRoleException e) {
  13.         return CommonResult.forbidden(e.getMessage());
  14.     }
  15. }
  • 我们现在有两个用户,admin用户具有ROLE_ADMIN角色,macro用户具有ROLE_USER角色;

  • 使用admin账号访问/brand/list接口可以正常访问;

  • 使用macro账号访问/brand/list接口无法正常访问,返回code403

权限认证

当我们给角色分配好权限,然后给用户分配好角色后,用户就拥有了这些权限。我们可以为每个接口分配不同的权限,拥有该权限的用户就可以访问该接口。这就是权限认证,接下来我们来实现下它。

  • 我们可以在Sa-Token的拦截器中配置路由规则,admin用户可以访问所有路径,而macro用户只有读取的权限,没有写、改、删的权限;

  1. /**
  2.  * Sa-Token相关配置
  3.  */
  4. @Configuration
  5. public class SaTokenConfig implements WebMvcConfigurer {
  6.     @Autowired
  7.     private IgnoreUrlsConfig ignoreUrlsConfig;
  8.     /**
  9.      * 注册sa-token拦截器
  10.      */
  11.     @Override
  12.     public void addInterceptors(InterceptorRegistry registry) {
  13.         registry.addInterceptor(new SaRouteInterceptor((req, resp, handler) -> {
  14.             // 获取配置文件中的白名单路径
  15.             List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
  16.             // 登录认证:除白名单路径外均需要登录认证
  17.             SaRouter.match(Collections.singletonList("/**"), ignoreUrls, StpUtil::checkLogin);
  18.             // 权限认证:不同接口, 校验不同权限
  19.             SaRouter.match("/brand/listAll", () -> StpUtil.checkPermission("brand:read"));
  20.             SaRouter.match("/brand/create", () -> StpUtil.checkPermission("brand:create"));
  21.             SaRouter.match("/brand/update/{id}", () -> StpUtil.checkPermission("brand:update"));
  22.             SaRouter.match("/brand/delete/{id}", () -> StpUtil.checkPermission("brand:delete"));
  23.             SaRouter.match("/brand/list", () -> StpUtil.checkPermission("brand:read"));
  24.             SaRouter.match("/brand/{id}", () -> StpUtil.checkPermission("brand:read"));
  25.         })).addPathPatterns("/**");
  26.     }
  27. }
  • 当用户无权限访问时,Sa-Token会抛出NotPermissionException异常,我们可以全局处理下;

  1. /**
  2.  * 全局异常处理
  3.  * Created by macro on 2020/2/27.
  4.  */
  5. @ControllerAdvice
  6. public class GlobalExceptionHandler {
  7.     /**
  8.      * 处理没有权限的异常
  9.      */
  10.     @ResponseBody
  11.     @ExceptionHandler(value = NotPermissionException.class)
  12.     public CommonResult handleNotPermissionException(NotPermissionException e) {
  13.         return CommonResult.forbidden(e.getMessage());
  14.     }
  15. }
  • 使用admin账号访问/brand/delete接口可以正常访问;

  • 使用macro账号访问/brand/delete无法正常访问,返回code403

 

总结

通过对Sa-Token的一波实践,我们可以发现它的API设计非常优雅,比起Shiro和Spring Security来说确实顺手多了。Sa-Token不仅提供了一系列强大的权限相关功能,还提供了很多标准的解决方案,比如Oauth2、分布式Session会话等,大家感兴趣的话可以研究下。

 

参考资料

Sa-Token的官方文档很全,也很良心,不仅提供了解决方式,还提供了解决思路,强烈建议大家去看下。

官方文档:http://sa-token.dev33.cn/

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/927269
推荐阅读
相关标签
  

闽ICP备14008679号