SpringBoot整合Shiro

- Maven依賴及配置

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- 數據庫連接-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <!-- spring session-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <!-- 熱部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

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

        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.2.3</version>
            <!-- <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/libs/shiro-redis-3.2.3.jar</systemPath> -->
        </dependency>

        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>

        <!-- lombok idea需要安裝lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.16</version>
        </dependency>


    </dependencies>
###數據庫連接配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.url=jdbc:mysql:///jpa
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect


###redis連接配置
spring.redis.database=5
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
#連接池最大阻塞時間,-1表示不限制
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
  • 項目結構
    在這裏插入圖片描述
  • 數據表設計
CREATE TABLE `sys_user` (
	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
	`username` VARCHAR(100) NOT NULL DEFAULT '',
	`pwd` VARCHAR(100) NOT NULL DEFAULT '',
	`create_time` DATETIME NULL DEFAULT NULL,
	PRIMARY KEY (`id`),
	UNIQUE INDEX `索引 2` (`username`)
)
COLLATE='utf8_general_ci';

CREATE TABLE `sys_role` (
	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
	`desc` VARCHAR(50) NULL DEFAULT '0',
	`create_time` DATETIME NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci';

CREATE TABLE `sys_user_role` (
	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
	`user_id` BIGINT(20) NOT NULL,
	`role_id` BIGINT(20) NOT NULL,
	`create_time` DATETIME NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci';


CREATE TABLE `sys_resource` (
	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(50) NOT NULL DEFAULT '',
	`parent_id` BIGINT NOT NULL DEFAULT 0,
	`url` VARCHAR(250) NOT NULL DEFAULT '0',
	`create_time` DATETIME NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci';

CREATE TABLE `sys_role_resource` (
	`id` BIGINT NOT NULL AUTO_INCREMENT,
	`role_id` BIGINT NOT NULL,
	`resource_id` BIGINT NOT NULL,
	`create_time` DATETIME NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci';
  • 集成代碼
package org.sang.config.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 權限框架
 */
@Configuration
public class ShiroConfig {

    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private String redisPort;
    @Value("${spring.redis.password}")
    private String redisPassword;
    @Value("${spring.redis.database}")
    private int redisDatabase;


    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        filterChainDefinitionMap.put("/security/logout", "anon");
        filterChainDefinitionMap.put("/security/login", "anon");
        filterChainDefinitionMap.put("/security/forget", "anon");
        filterChainDefinitionMap.put("/offlineBatch/**", "anon");
        filterChainDefinitionMap.put("/experimental/**", "anon");
        filterChainDefinitionMap.put("/modelCall/**", "anon");
        filterChainDefinitionMap.put("/realTime/**", "anon");
        //filterChainDefinitionMap.put("/experimental/train/**", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/v2/**", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setLoginUrl("/user/unAuth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("SHA-1");//散列算法:這裏使用SHA-1算法;
        hashedCredentialsMatcher.setHashIterations(1024);//散列的次數
        return hashedCredentialsMatcher;
    }

    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        //myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }


    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        //Session會話數據存在Redis
        securityManager.setSessionManager(sessionManager());
        //用戶權限記錄Cache到Redis. Subject.logout()退出時觸發緩存清理。如果沒有調用退出接口,權限緩存到期自動消失(默認30分鐘)
        securityManager.setCacheManager(cacheManager());
        return securityManager;
    }

    public SessionManager sessionManager() {
        MySessionManager mySessionManager = new MySessionManager();
        mySessionManager.setGlobalSessionTimeout(1800000); //session過期時間 優先級比server.servlet.session.timeout高
        mySessionManager.setSessionDAO(redisSessionDAO());
//        mySessionManager.setSessionValidationInterval(sessionValidationInterval);
//        mySessionManager.setSessionValidationSchedulerEnabled(true);
        return mySessionManager;
//        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//        sessionManager.setSessionDAO(redisSessionDAO());
//        return sessionManager;
    }


    /**
     * RedisClusterManager
     * @return
     */
    /*public RedisClusterManager redisManager() {
        RedisClusterManager redisClusterManager = new RedisClusterManager();
        redisClusterManager.setHost(host);
        return redisClusterManager;
    }


    @Bean("myRedisCacheManager")
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }*/

    /**
     * cacheManager 緩存 redis實現
     * 使用的是shiro-redis開源插件
     *
     * @return
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis開源插件
     *
     * @return
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(redisHost + ":" + redisPort);
        redisManager.setPassword(redisPassword);
        redisManager.setDatabase(redisDatabase);
        return redisManager;
    }


    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());

        return redisSessionDAO;
    }

    /**
     * 開啓shiro aop註解支持.
     * 使用代理方式;所以需要開啓代碼支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

}

package org.sang.config.shiro;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.CollectionUtils;
import org.crazycake.shiro.RedisSessionDAO;
import org.sang.dao.SysUserDao;
import org.sang.model.SysUser;
import org.sang.service.SysResourceService;

import javax.annotation.Resource;
import java.util.List;

public class MyShiroRealm extends AuthorizingRealm {

    @Resource
    private SysResourceService sysResourceService;

    @Resource
    private SysUserDao sysUserDao;

    @Resource
    private RedisSessionDAO redisSessionDAO;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("權限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        SysUser user = (SysUser) principals.getPrimaryPrincipal();
        try {
            List<String> permissionList = sysResourceService.getResourceByUserId(user.getId());
            if (!CollectionUtils.isEmpty(permissionList)) {
                for (String permission : permissionList) {
                    if (permission != null && !"".equals(permission)) {
                        String[] permissions = permission.split(",");
                        for (String result : permissions) {
                            authorizationInfo.addStringPermission(result);
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return authorizationInfo;
    }

    /*主要是用來進行身份認證的,也就是說驗證用戶輸入的賬號和密碼是否正確。*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        //獲取用戶的輸入的賬號.
        String username = (String) token.getPrincipal();
        String pwd = new String((char[]) token.getCredentials());
        SysUser user = sysUserDao.findByUsername(username);

        if (user == null || !user.getPwd().equalsIgnoreCase(pwd)) {
            throw new UnknownAccountException();
        }

        //鹽值密碼
//        byte[] salt = HexUtils.fromHexString(user.getPwd().substring(0, 16));
//        Session session = SecurityUtils.getSubject().getSession();
//        singleLogin.singleLogin(username, pwd, user, salt, session.getId());

//        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
//                user, //用戶
//                user.getPwd().substring(16), //密碼
//                ByteSource.Util.bytes(salt),//salt=username+salt
//                getName()  //realm name
//        );

        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user, //傳入用戶對象,權限檢測時需要讀取用戶信息
                user.getPwd(), //密碼
                getName()  //realm name
        );

        return authenticationInfo;
    }
}

package org.sang.config.shiro;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

public class MySessionManager extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "mySessionId";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //否則按默認規則從cookie取sessionId
            return super.getSessionId(request, response);
        }
    }
}

package org.sang.controller;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.sang.common.Response;
import org.sang.vo.UserLoginVo;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.Serializable;

@RestController
@RequestMapping("security")
public class SecurityController {

    /**
     * 用戶登錄
     * @param userLoginVo
     * @return
     */
    @PostMapping("login")
    public Response<String> login(@Validated @RequestBody UserLoginVo userLoginVo) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userLoginVo.getUsername(), userLoginVo.getPwd());
        try {
            subject.login(token);
            Serializable serializable = subject.getSession().getId();
            return new Response(true, null, serializable);
        } catch (Exception e) {
            e.printStackTrace();
            return new Response(false, null, e.getMessage());
        }
    }

    @RequestMapping("logout")
    public Response logout() {
        try {
            Subject subject = SecurityUtils.getSubject();
            if (!ObjectUtils.isEmpty(subject)) {
                subject.logout();
            }
            return new Response(true, "退出成功", null);
        } catch (Exception e) {
            return new Response(false, "系統異常", null);
        }

    }

}

  • Postman測試
    在這裏插入圖片描述
發佈了18 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章