SpringBoot2 integrates Shiro framework for user rights management

Keywords: Programming Shiro Session github Apache

Source code for this article: GitHub. Click here || GitEE. Click here

1. Introduction to Shiro

1. Basic concepts

Apache Shiro is a powerful and easy-to-use Java security framework for authentication, authorization, password, and session management.Shiro is cleverly designed as a security framework.Shiro's application does not depend on any container; it can be used not only in JavaEE, but also in JavaSE environments.

2. Core Role

1) Subject: Authentication Subject

The user representing the current system is the user. In Shiro's authentication, the authentication principal is usually userName and passWord, or other unique identification related to the user.

2) SecurityManager: Security Manager

The core component of the Shiro architecture through which user authentication and authorization can be coordinated with other components.In fact, SecurityManager is the controller of the Shiro framework.

3) Realm: Domain Object

Defines how to access data to connect to different data sources, such as relational databases, configuration files, and so on.

3. Core Ideas

Shiro itself maintains users and permissions, completing user authentication and authorization through injection of Subject user principals and Realm domain objects.

2. Integrating the SpringBoot2 framework

1. Core Dependency

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

2. Shiro Core Configuration

@Configuration
public class ShiroConfig {
    /**
     * Session Manager: session management
     * That is, the user logs in and then has a session, and all of its information is in the session before exiting.
     * Sessions can be in a normal JavaSE environment or a Web environment.
     */
    @Bean("sessionManager")
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //Set session expiration time
        sessionManager.setGlobalSessionTimeout(60 * 60 * 1000);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        // Remove JSESSIONID from url at shiro login
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }

    /**
     * SecurityManager: Security Manager
     */
    @Bean("securityManager")
    public SecurityManager securityManager(UserRealm userRealm, SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setSessionManager(sessionManager);
        securityManager.setRealm(userRealm);
        return securityManager;
    }
    /**
     * ShiroFilter Is the entry point for the entire Shiro, used to intercept requests requiring security control for processing
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        shiroFilter.setLoginUrl("/userLogin");
        shiroFilter.setUnauthorizedUrl("/");
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/userLogin", "anon");
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }
    /**
     * Manage the life cycle of some bean s in Shiro
     */
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    /**
     * Scan context for all Advistors
     * Apply these Advisor s to all Bean s that meet your starting point.
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }
    /**
     * Match all methods with Shiro authentication annotations
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

3. Domain Object Configuration

@Component
public class UserRealm extends AuthorizingRealm {
    @Resource
    private SysUserMapper sysUserMapper ;
    @Resource
    private SysMenuMapper sysMenuMapper ;
    /**
     * Authorization (called when validating permissions)
     * Get User Rights Collection
     */
    @Override
    public AuthorizationInfo doGetAuthorizationInfo
    (PrincipalCollection principals) {
        SysUserEntity user = (SysUserEntity)principals.getPrimaryPrincipal();
        if(user == null) {
            throw new UnknownAccountException("Account does not exist");
        }
        List<String> permsList;
        //The default user has the highest privileges
        List<SysMenuEntity> menuList = sysMenuMapper.selectList();
        permsList = new ArrayList<>(menuList.size());
        for(SysMenuEntity menu : menuList){
            permsList.add(menu.getPerms());
        }
        //User Rights List
        Set<String> permsSet = new HashSet<>();
        for(String perms : permsList){
            if(StringUtils.isEmpty(perms)){
                continue;
            }
            permsSet.addAll(Arrays.asList(perms.trim().split(",")));
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permsSet);
        return info;
    }
    /**
     * Authentication (called at login)
     * Verify user login
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken)authToken;
        //Query user information
        SysUserEntity user = sysUserMapper.selectOne(token.getUsername());
        //Account does not exist
        if(user == null) {
            throw new UnknownAccountException("Incorrect account or password");
        }
        //account lockout
        if(user.getStatus() == 0){
            throw new LockedAccountException("Account is locked,Please contact your administrator");
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo
                (user, user.getPassword(),
                        ByteSource.Util.bytes(user.getSalt()),
                        getName());
        return info;
    }
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
        shaCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);
        shaCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);
        super.setCredentialsMatcher(shaCredentialsMatcher);
    }
}

4. Core Tool Classes

public class ShiroUtils {
    /**  encryption algorithm */
    public final static String hashAlgorithmName = "SHA-256";
    /**  Number of loops */
    public final static int hashIterations = 16;
    public static String sha256(String password, String salt) {
        return new SimpleHash(hashAlgorithmName, password, salt, hashIterations).toString();
    }
    // Get a test account admin
    public static void main(String[] args) {
        // 3743a4c09a17e6f2829febd09ca54e627810001cf255ddcae9dabd288a949c4a
        System.out.println(sha256("admin","123")) ;
    }
    /**
     * Get Session
     */
    public static Session getSession() {
        return SecurityUtils.getSubject().getSession();
    }
    /**
     * Subject: Principal, representing the current "user"
     */
    public static Subject getSubject() {
        return SecurityUtils.getSubject();
    }
    public static SysUserEntity getUserEntity() {
        return (SysUserEntity)SecurityUtils.getSubject().getPrincipal();
    }
    public static Long getUserId() {
        return getUserEntity().getUserId();
    }
    public static void setSessionAttribute(Object key, Object value) {
        getSession().setAttribute(key, value);
    }
    public static Object getSessionAttribute(Object key) {
        return getSession().getAttribute(key);
    }
    public static boolean isLogin() {
        return SecurityUtils.getSubject().getPrincipal() != null;
    }
    public static void logout() {
        SecurityUtils.getSubject().logout();
    }
}

5. Custom permission exception prompt

@RestControllerAdvice
public class ShiroException {
    @ExceptionHandler(AuthorizationException.class)
    public String authorizationException (){
        return "Sorry, you do not have permission to access this content!";
    }
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e){
        return "System exception!";
    }
}

3. Case Demonstration Code

1. Test Interface

@RestController
public class ShiroController {
    private static Logger LOGGER = LoggerFactory.getLogger(ShiroController.class) ;
    @Resource
    private SysMenuMapper sysMenuMapper ;
    /**
     * Logon Test
     * http://localhost:7011/userLogin?userName=admin&passWord=admin
     */
    @RequestMapping("/userLogin")
    public void userLogin (
            @RequestParam(value = "userName") String userName,
            @RequestParam(value = "passWord") String passWord){
        try{
            Subject subject = ShiroUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(userName, passWord);
            subject.login(token);
            LOGGER.info("Login Successful");
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * The server must request the above login interface before requesting it each time it restarts
     * http://localhost:7011/menu/list Get all menu lists
     * Permission requirements: sys:user:shiro
     */
    @RequestMapping("/menu/list")
    @RequiresPermissions("sys:user:shiro")
    public List list(){
        return sysMenuMapper.selectList() ;
    }
    /**
     * User does not have this permission and cannot access
     * Permission requirements: ccc:ddd:bbb
     */
    @RequestMapping("/menu/list2")
    @RequiresPermissions("ccc:ddd:bbb")
    public List list2(){
        return sysMenuMapper.selectList() ;
    }
    /**
     * Exit the test without any permission
     */
    @RequestMapping("/userLogOut")
    public String logout (){
        ShiroUtils.logout();
        return "success" ;
    }
}

2. Test process

1),Get permission after login
http://localhost:7011/userLogin?userName=admin&passWord=admin
2),Access Rights Interface
http://localhost:7011/menu/list
3),Access to unprivileged interfaces
http://localhost:7011/menu/list2
4),Log out
http://localhost:7011/userLogOut

4. Source code address

GitHub Address: Know a smile
https://github.com/cicadasmile/middle-ware-parent
 Code Cloud Address: Know a smile
https://gitee.com/cicadasmile/middle-ware-parent

Posted by mysqlnewjack on Tue, 03 Sep 2019 17:40:53 -0700