When we were working on the SpringBoot project, authentication and authorization was an essential feature! We often choose authorization authentication frameworks such as Shiro and Spring Security, but these frameworks are cumbersome to use and not powerful enough. Recently found a powerful authority authentication framework SA-Token, it is simple to use, ELEGANT API design, recommended to everyone!

SpringBoot e-commerce project mall (50K + STAR) address: github.com/macrozheng/…

Sa – Token profile

Sa-token is a lightweight Java permission authentication framework, which can be used to solve a series of permission related problems such as login authentication, permission authentication, Session Session, single sign-on (SSO), OAuth2.0, and micro service network authentication.

The framework integration is simple, out of the box, and the API design is elegant. With Sa-Token, you can implement the authentication part of your system in a very simple way, sometimes with just one line of code.

The sa-token has many functions. For details, see the following figure.

use

Using sa-Token in SpringBoot is very simple, and then we use it to implement the most common authentication and authorization functions, including login authentication, role authentication, and permission authentication.

Integration and Configuration

The integration and configuration of sa-Token is simple and right out of the box.

  • First we need in the projectpom.xmlTo add sa-token dependencies.
<! -- Sa-token authentication -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.24.0</version>
</dependency>
Copy the code
  • Then, inapplication.ymlTo add the configuration of sa-token, considering to support the front and back end separation project, we turn off reading Token from cookie, and read Token from head instead.
# Sa - Token configuration
sa-token:
  # token name (also cookie name)
  token-name: Authorization
  # Token validity period, in seconds. -1 indicates that the token will never expire
  timeout: 2592000
  # Token temporary validity period (if no operation is performed within the specified period, the token is regarded as expired), in seconds
  activity-timeout: - 1
  # allow concurrent logins with the same account (false)
  is-concurrent: true
  # Whether to share one token when multiple users log in to the same account (if false, create one token for each login)
  is-share: false
  Style # token
  token-style: uuid
  # Whether to output operation logs
  is-log: false
  Whether to read token from cookie
  is-read-cookie: false
  Whether to read the token from head
  is-read-head: true
Copy the code

Login authentication

In the management system, login authentication is required except for the login interface. Sa-token authentication is the most convenient. The following describes how to implement the authentication.

  • Implementing login authentication is very simple, first inUmsAdminControllerTo add a login interface;
/** * Created by macro on 2018/4/26. */
@Controller
@API (tags = "UmsAdminController", description = "background user management ")
@RequestMapping("/admin")
public class UmsAdminController {
    @Autowired
    private UmsAdminService adminService;

    @apiOperation (value = "return token after login ")
    @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("Wrong username or password");
        }
        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", saTokenInfo.getTokenValue());
        tokenMap.put("tokenHead", saTokenInfo.getTokenName());
        returnCommonResult.success(tokenMap); }}Copy the code
  • Then, inUmsAdminServiceImplAdd log-specific logic that validates the password first and then invokesStpUtil.login(adminUser.getId())Can realize login, call API one line to fix;
/** * 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;
        }
        // After the password verification is successful, you can log in with one line of code
        StpUtil.login(adminUser.getId());
        // Obtain the Token information of the current login user
        saTokenInfo = StpUtil.getTokenInfo();
        returnsaTokenInfo; }}Copy the code
  • We add another test interface to query the current login status, returntrueIndicates that you have logged in.
/** * Created by macro on 2020/10/15. */
@Slf4j
@Service
public class UmsAdminServiceImpl implements UmsAdminService {
    @apiOperation (value = "query current login status ")
    @RequestMapping(value = "/isLogin", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult isLogin(a) {
        returnCommonResult.success(StpUtil.isLogin()); }}Copy the code
  • After that, Swagger can access the login interface to obtain the Token, using the account asadmin:123456, visit address:http://localhost:8088/swagger-ui/

  • Then, inAuthorizationAdd the acquired token to the request header;

  • access/admin/isLoginInterface,dataProperty will returntrue“Indicates that you are logged in.

  • Next we need to add login authentication to all interfaces except the login interface and add the Java configuration class of SA-TokenSaTokenConfigRegister a route interceptorSaRouteInterceptorHere’s ourIgnoreUrlsConfigThe configuration reads the whitelist configuration from the configuration file.
/** * Sa-token configuration */
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {

    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;

    /** * Register the sa-token interceptor */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaRouteInterceptor((req, resp, handler) -> {
            // Get the whitelist path in the configuration file
            List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
            // Login authentication: Requires login authentication except for the whitelist path
            SaRouter.match(Collections.singletonList("/ * *"), ignoreUrls, StpUtil::checkLogin);
        })).addPathPatterns("/ * *"); }}Copy the code
  • application.ymlThe whitelist configuration in the file is as follows: Open Swagger access path and static resource path.
Access the whitelist path
secure:
  ignored:
    urls:
      - /
      - /swagger-ui/
      - /*.html
      - /favicon.ico
      - /**/*.html
      - /**/*.css
      - /**/*.js
      - /swagger-resources/**
      - /v2/api-docs/**
      - /actuator/**
      - /admin/login
      - /admin/isLogin
Copy the code
  • An sa-token is thrown because an interface is accessed in an unlogged stateNotLoginExceptionException, so we need to handle it globally;
/** * Created by macro on 2020/2/27. */
@ControllerAdvice
public class GlobalExceptionHandler {

    /** * Handle unlogged exceptions */
    @ResponseBody
    @ExceptionHandler(value = NotLoginException.class)
    public CommonResult handleNotLoginException(NotLoginException e) {
        returnCommonResult.unauthorized(e.getMessage()); }}Copy the code
  • Then when we access the interface in login state, we can get the data;

  • Return if we cannot access the interface normally when we are not logged in (without token)codefor401.

Role of certification

ROLE authentication means that we define a set of rules. For example, role-admin can access all resources under /brand, while ROLE_USER can only access /brand/listAll. Next, we implement ROLE authentication.

  • First we need to extend the SA-tokenStpInterfaceInterface, through the implementation method to return the user’s role code and permission code;
/** * Custom permission validation interface extension */
@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));
        returnCollections.singletonList(adminUser.getRole().getName()); }}Copy the code
  • Then configure the routing rules in the Sa-Token interceptor,ROLE_ADMINThe role has access to all paths, andROLE_USERCan only access/brand/listAllThe path;
/** * Sa-token configuration */
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {

    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;

    /** * Register the sa-token interceptor */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaRouteInterceptor((req, resp, handler) -> {
            // Get the whitelist path in the configuration file
            List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
            // Login authentication: Requires login authentication except for the whitelist path
            SaRouter.match(Collections.singletonList("/ * *"), ignoreUrls, StpUtil::checkLogin);
            // Role authentication: ROLE_ADMIN can access all interfaces. ROLE_USER can access only all interfaces
            SaRouter.match("/brand/listAll", () -> {
                StpUtil.checkRoleOr("ROLE_ADMIN"."ROLE_USER");
                // Force to exit the matching chain
                SaRouter.stop();
            });
            SaRouter.match("/brand/**", () -> StpUtil.checkRole("ROLE_ADMIN"));
        })).addPathPatterns("/ * *"); }}Copy the code
  • The sa-token is thrown if the user is not an authorized roleNotRoleExceptionExceptions, we can handle them globally;
/** * Created by macro on 2020/2/27. */
@ControllerAdvice
public class GlobalExceptionHandler {

    /** * handle exception with no role */
    @ResponseBody
    @ExceptionHandler(value = NotRoleException.class)
    public CommonResult handleNotRoleException(NotRoleException e) {
        returnCommonResult.forbidden(e.getMessage()); }}Copy the code
  • We now have two users,adminThe user hasROLE_ADMINRole,macroThe user hasROLE_USERRole;

  • useadminAccount access/brand/listThe interface can be accessed normally.

  • usemacroAccount access/brand/listThe interface cannot be accessedcodefor403.

Authority certification

When we assign permissions to roles, and then 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 we’re going to implement it.

  • We can configure routing rules in the Sa-Token interceptor,adminThe user has access to all paths, whilemacroUsers can only read, but not write, modify, or delete.
/** * Sa-token configuration */
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {

    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;

    /** * Register the sa-token interceptor */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaRouteInterceptor((req, resp, handler) -> {
            // Get the whitelist path in the configuration file
            List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
            // Login authentication: Requires login authentication except for the whitelist path
            SaRouter.match(Collections.singletonList("/ * *"), ignoreUrls, StpUtil::checkLogin);
            // Permission authentication: Verifies different permissions for different interfaces
            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("/ * *"); }}Copy the code
  • If the user has no access permission, the sa-token is thrownNotPermissionExceptionExceptions, we can handle them globally;
/** * Created by macro on 2020/2/27. */
@ControllerAdvice
public class GlobalExceptionHandler {

    /** * handle exceptions without permissions */
    @ResponseBody
    @ExceptionHandler(value = NotPermissionException.class)
    public CommonResult handleNotPermissionException(NotPermissionException e) {
        returnCommonResult.forbidden(e.getMessage()); }}Copy the code
  • useadminAccount access/brand/deleteThe interface can be accessed normally.

  • usemacroAccount access/brand/deleteUnable to access, returncodefor403.

conclusion

After a wave of practice with Sa-Token, we can see that its API design is very elegant, and it is certainly more comfortable than Shiro and Spring Security. Sa-token not only provides a series of powerful permission-related functions, but also provides many standard solutions, such as Oauth2, distributed Session sessions, etc. If you are interested, you can explore.

The resources

The official documentation of SA-Token is very comprehensive and conscientious. It not only provides solutions, but also provides solutions. I strongly recommend that you take a look at it.

Official document: sa-token.dev33.cn/

Project source code address

Github.com/macrozheng/…

In this paper, making github.com/macrozheng/… Already included, welcome everyone Star!