4
头图
When we are working on the SpringBoot project, authentication and authorization is an indispensable function! We often choose permission authentication frameworks such as Shiro and Spring Security to implement, but these frameworks are a bit cumbersome to use, and their functions are not powerful enough. I recently discovered a powerful authorization authentication framework, Sa-Token, which is simple to use and elegant in API design. I recommend it to everyone!

SpringBoot actual combat e-commerce project mall (50k+star) address: https://github.com/macrozheng/mall

Introduction to Sa-Token

Sa-Token is a lightweight Java permission authentication framework that can be used to solve a series of permission-related problems such as login authentication, permission authentication, Session session, single sign-on, OAuth2.0, microservice gateway authentication and so on.

The framework is simple to integrate, out-of-the-box, and API design is elegant. With Sa-Token, you will implement the authorization authentication part of the system in an extremely simple way, and sometimes only one line of code is needed to achieve the function.

Sa-Token has very complete functions, please refer to the figure below for details.

use

It is very simple to use Sa-Token in SpringBoot. Next, we will use it to implement the most commonly used authentication and authorization functions, including login authentication, role authentication, and permission authentication.

Integration and configuration

The integration and configuration of Sa-Token are very simple, and it is worthy of being used out of the box.
  • First, we need to add Sa-Token related dependencies pom.xml
<!-- Sa-Token 权限认证 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.24.0</version>
</dependency>
  • Then application.yml . Considering to support front-end and back-end separation projects, we turn off reading token from cookie and read token from head instead.
# Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: Authorization
  # token有效期,单位秒,-1代表永不过期
  timeout: 2592000
  # token临时有效期 (指定时间内无操作就视为token过期),单位秒
  activity-timeout: -1
  # 是否允许同一账号并发登录 (为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

Login authentication

In the management system, in addition to the login interface, login authentication is basically required. It is the most convenient to use route interception authentication in Sa-Token. Let's implement it below.
  • Realizing login authentication is very simple, first add a login interface 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);
    }
}
  • Then UmsAdminServiceImpl , first verify the password, and then call StpUtil.login(adminUser.getId()) to achieve login, call the API one line to get it;
/**
 * 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;
        }
        // 密码校验成功后登录,一行代码实现登录
        StpUtil.login(adminUser.getId());
        // 获取当前登录用户Token信息
        saTokenInfo = StpUtil.getTokenInfo();
        return saTokenInfo;
    }
}
  • We add another test interface to query the current login status, and return true indicate that it has been logged in;
/**
 * Created by macro on 2020/10/15.
 */
@Slf4j
@Service
public class UmsAdminServiceImpl implements UmsAdminService {
    @ApiOperation(value = "查询当前登录状态")
    @RequestMapping(value = "/isLogin", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult isLogin() {
        return CommonResult.success(StpUtil.isLogin());
    }
}

  • Then add the obtained token to the Authorization

  • Access the /admin/isLogin interface, the data attribute will return true , indicating that you are already logged in;

  • Next, we need to add login authentication to all interfaces except the login interface, add the Sa-Token Java configuration class SaTokenConfig , register a route interceptor SaRouteInterceptor , here our IgnoreUrlsConfig configuration will read the whitelist configuration from the configuration file;
/**
 * 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 whitelist configuration in the 06125c05a6e8de file is as follows, pay attention to the open access path and static resource path of Swagger;
# 访问白名单路径
secure:
  ignored:
    urls:
      - /
      - /swagger-ui/
      - /*.html
      - /favicon.ico
      - /**/*.html
      - /**/*.css
      - /**/*.js
      - /swagger-resources/**
      - /v2/api-docs/**
      - /actuator/**
      - /admin/login
      - /admin/isLogin
  • NotLoginException exception when accessing the interface when not logged in, we need to deal with it globally;
/**
 * 全局异常处理
 * 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());
    }
}
  • Later, when we access the interface in the logged-in state, we can get the data;

  • When we are not logged in (without token), we cannot access the interface normally, and the return code is 401 .

Role authentication

Role authentication means that we define a set of rules. For example, the ROLE-ADMIN can access all resources under /brand ROLE_USER can only access /brand/listAll . Next, let's implement role authentication.
  • First, we need to extend the StpInterface interface of Sa-Token, and return the user's role code and permission code through the implementation method;
/**
 * 自定义权限验证接口扩展
 */
@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());
    }
}
  • Then configure routing rules in the Sa-Token interceptor, the ROLE_ADMIN can access all paths, while ROLE_USER can only access the /brand/listAll path;
/**
 * 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只能访问查询全部接口
            SaRouter.match("/brand/listAll", () -> {
                StpUtil.checkRoleOr("ROLE_ADMIN","ROLE_USER");
                //强制退出匹配链
                SaRouter.stop();
            });
            SaRouter.match("/brand/**", () -> StpUtil.checkRole("ROLE_ADMIN"));
        })).addPathPatterns("/**");
    }
}
  • When the user is not allowed to access the role, Sa-Token will throw a NotRoleException exception, we can deal with it globally;
/**
 * 全局异常处理
 * 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());
    }
}
  • We now have two users, the admin user has the ROLE_ADMIN role, and the macro user has the ROLE_USER role;

  • Use the admin account to access the /brand/list interface, which can be accessed normally;

  • Using the macro account to access the /brand/list interface cannot be accessed normally, the return code is 403 .

Authority authentication

When we assign permissions to roles, and then assign roles to users, users have these permissions. We can assign different permissions to each interface, and users with that permission can access the interface. This is permission authentication, and then we will implement it.
  • We can configure routing rules in the Sa-Token interceptor. The admin user can access all paths, while the macro user only has the permission to read, without the permission to write, modify, or delete;
/**
 * 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);
            // 权限认证:不同接口, 校验不同权限
            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("/**");
    }
}
  • When the user does not have permission to access, Sa-Token will throw a NotPermissionException exception, we can deal with it globally;
/**
 * 全局异常处理
 * Created by macro on 2020/2/27.
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理没有权限的异常
     */
    @ResponseBody
    @ExceptionHandler(value = NotPermissionException.class)
    public CommonResult handleNotPermissionException(NotPermissionException e) {
        return CommonResult.forbidden(e.getMessage());
    }
}
  • Use the admin account to access the /brand/delete interface, which can be accessed normally;

  • Using the macro account to access /brand/delete cannot be accessed normally, and the return code is 403 .

Summarize

Through a wave of practice on Sa-Token, we can find that its API design is very elegant, and it is indeed much smoother than Shiro and Spring Security. Sa-Token not only provides a series of powerful permissions-related functions, but also provides many standard solutions, such as Oauth2, distributed session sessions, etc. If you are interested, you can study it.

Reference

The official document of Sa-Token is very complete and conscientious. It not only provides solutions, but also provides solutions. It is strongly recommended that you check it out.

Official document: http://sa-token.dev33.cn/

Project source code address

https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-sa-token

This article GitHub https://github.com/macrozheng/mall-learning has been included, welcome to Star!

macrozheng
1.1k 声望1.3k 粉丝