springboot + shiro + cas 实现 登录 + 授权 + sso单点登录 (二)

springboot + shiro + cas 实现 登录 + 授权 + sso单点登录 (一)

本篇文章我们将使用 springboot + redis + shiro 开发 角色 + 权限 功能。

1、新建一个maven+springboot项目,我的工程结构为:

sso_login 是一个maven工程,里面有两个module,分别是 member 和 order 两个 springboot 的项目,member 主要是登录相关,order主要是以后为了测试 从member系统单点到order系统。

2、member工程结构

3、sso_login的pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gane.maple</groupId>
    <artifactId>sso_login</artifactId>
    <packaging>pom</packaging>
    <version>1.0</version>

    <modules>
        <module>member</module>
        <module>order</module>
    </modules>

    <properties>
        <spring.boot.version>2.2.6.RELEASE</spring.boot.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


</project>

4、member项目的pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.gane.maple</groupId>
        <artifactId>sso_login</artifactId>
        <version>1.0</version>
    </parent>

    <groupId>com.gane.maple.member</groupId>
    <artifactId>member</artifactId>
    <version>1.0.0</version>
    <name>member</name>
    <description>member system</description>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
            <exclusions>
                <exclusion>
                    <groupId>com.puppycrawl.tools</groupId>
                    <artifactId>checkstyle</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- apache -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!-- thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- 热启动 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

5、application.properties文件

# redis setting
jedis.pool.host=xxxxx
jedis.pool.port=6379
jedis.pool.timeout=300000
jedis.pool.password=xxxxx
jedis.pool.database=0
jedis.pool.config.maxTotal=100
jedis.pool.config.maxIdle=10
jedis.pool.config.maxWaitMillis=1000

#thymeleaf
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false

6、templates下面的页面,除了login.html,其他的都一样,只是说明不一样。

index.html

<!DOCTYPE html>
<!--解决th报错 -->
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>thymeleaf</title>
</head>
<body>
<h1> This is index page!</h1>
<h1 th:text="${userName}"></h1>
</body>
</html>

user.html

<!DOCTYPE html>
<!--解决th报错 -->
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>thymeleaf</title>
</head>
<body>
<h1> This is user page!</h1>
<h1 th:text="${userName}"></h1>
</body>
</html>

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
    <title>登录</title>
    <link rel="stylesheet" type="text/css" href="/css/common.css" />
</head>
<body>
<form th:action="@{/login}" method="post">
    <div>
        <!--/*@thymesVar id="error" type=""*/-->
        <span id="basic-addon0">&nbsp;</span>
        <span style="font-size: 12px;color: red" th:text="${error}" aria-describedby="basic-addon0"></span>
        <br />
    </div>
    <div>
        <span id="basic-addon1">@</span>
        <input id="user_name" name="username" type="text" placeholder="用户名" aria-describedby="basic-addon1" />

    </div>
    <br />
    <div>
        <span id="basic-addon2">@</span>
        <input id="password" name="password" type="password" placeholder="密码" aria-describedby="basic-addon2" />
    </div>
    <br />
    <button type="submit" style="width:190px;">登 录</button>

</form>
</body>
</html>

7、User 类

package com.gane.maple.member.model;

import lombok.Data;

import java.io.Serializable;

/**
 * @Description TODO
 * @Date 2020/4/9 20:09
 * @Created by 王弘博
 */
@Data
public class User implements Serializable {

    private String username;
    private String password;
}

8、ShiroController

package com.gane.maple.member.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

/**
 * @Description TODO
 * @Date 2020/4/9 18:49
 * @Created by 王弘博
 */
@Controller
public class ShiroController {

    private static final Logger logger = LoggerFactory.getLogger(ShiroController.class);

    @GetMapping("/index")
    public String index(Model model) {
        model.addAttribute("userName", "maple");
        return "/index";

    }

    @GetMapping("/user")
    public String user(Model model) {
        model.addAttribute("userName", "maple");
        return "/user";

    }

    @RequiresPermissions("admin:manage")
    @GetMapping("/manage")
    public String manage(Model model) {
        model.addAttribute("userName", "maple");
        return "/user_manage";
    }

    @RequiresPermissions("admin:query")
    @GetMapping("/query")
    public String query(Model model) {
        model.addAttribute("userName", "maple");
        return "/user_query";
    }

    @GetMapping("/login")
    public String login(Model model) {
        model.addAttribute("userName", "maple");
        return "/login";
    }

    @PostMapping("/login")
    public String login(String username, String password) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password, true);
        try {
            logger.info("对用户[" + username + "]进行登录验证..验证开始");
            subject.login(token);
            logger.info("对用户[" + username + "]进行登录验证..验证通过");
        } catch (UnknownAccountException uae) {
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户");
        } catch (IncorrectCredentialsException ice) {
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");
        } catch (LockedAccountException lae) {
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");
        } catch (ExcessiveAttemptsException eae) {
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");
        } catch (AuthenticationException ae) {
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");
            ae.printStackTrace();
        }
        if (subject.isAuthenticated()) {
            logger.info("用户[" + username + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
            return "redirect:/user";
        }
        token.clear();
        return "redirect:/login";

    }

    @GetMapping("/logout")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            subject.logout();
        }
        return "redirect:/login";
    }

    @GetMapping("/403")
    public String unauthorizedRole() {
        logger.info("------没有权限-------");
        return "403";
    }
}

9、RedisConfiguration

package com.gane.maple.member.config.redis;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import javax.annotation.Resource;

/**
 * @Description redis配置
 * @Date 2019/7/11 13:09
 * @Created by 王弘博
 */
@Configuration
public class RedisConfiguration {

    @Bean(name = "jedis.pool")
    @Resource
    public JedisPool jedisPool(@Qualifier("jedis.pool.config") JedisPoolConfig config,
                               @Value("${jedis.pool.host}") String host,
                               @Value("${jedis.pool.port}") int port,
                               @Value("${jedis.pool.timeout}") int timeout,
                               @Value("${jedis.pool.password}") String password,
                               @Value("${jedis.pool.database}") int database) {
        return new JedisPool(config, host, port, timeout, password, database);
    }

    @Bean(name = "jedis.pool.config")
    public JedisPoolConfig jedisPoolConfig(@Value("${jedis.pool.config.maxTotal}") int maxTotal,
                                           @Value("${jedis.pool.config.maxIdle}") int maxIdle,
                                           @Value("${jedis.pool.config.maxWaitMillis}") int maxWaitMillis) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMaxWaitMillis(maxWaitMillis);
        return config;
    }
}

10、RedisClient(没有用到)

package com.gane.maple.member.config.redis;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description redis常用方法
 * @Date 2019/7/11 13:09
 * @Created by 王弘博
 */
@Component
public class RedisClient {

    private Logger logger = LoggerFactory.getLogger(RedisClient.class);

    @Resource
    private JedisPool jedisPool;

    public Long del(String key) {
        Long value = null;
        Jedis jedis = null;
        try {
            jedis = this.getResource();
            if (jedis.exists(key)) {
                value = jedis.del(key);
                this.logger.debug("del {} = {}", key, value);
            }
        } catch (Exception e) {
            this.logger.error("del {} = {}", new Object[]{key, value, e});
        } finally {
            this.release(jedis);
        }
        return value;
    }

    public String get(String key) {
        String value = null;
        Jedis jedis = null;
        try {
            jedis = this.getResource();
            if (jedis.exists(key)) {
                value = jedis.get(key);
                value = StringUtils.isNotBlank(value) && !"nil".equalsIgnoreCase(value) ? value : null;
                this.logger.debug("get {} = {}", key, value);
            }
        } catch (Exception e) {
            this.logger.error("get {} = {}", new Object[]{key, value, e});
        } finally {
            this.release(jedis);
        }
        return value;
    }

    public String set(String key, String value) {
        return this.set(key, value, 0);
    }

    public String set(String key, String value, int expire) {
        Jedis jedis = null;
        String result = null;
        try {
            jedis = this.getResource();
            result = jedis.set(key, value);
            if (expire != 0) {
                jedis.expire(key, expire);
            }
            logger.debug("set {} = {}", key, value);
        } catch (Exception e) {
            logger.error("set {} = {}", new Object[]{key, value, e});
        } finally {
            this.release(jedis);
        }
        return result;
    }

    public boolean exists(String key) {
        boolean result = false;
        Jedis jedis = null;
        try {
            jedis = this.getResource();
            result = jedis.exists(key);
        } catch (Exception e) {
            logger.error("exists {}", key, e);
        } finally {
            this.release(jedis);
        }
        return result;
    }

    /**
     * ttl
     *
     * @param key
     * @return
     */
    public Long ttl(String key) {
        Long result = 0L;
        Jedis jedis = null;
        try {
            jedis = this.getResource();
            result = jedis.ttl(key);
        } catch (Exception e) {
            logger.error("exists {}", key, e);
        } finally {
            this.release(jedis);
        }
        return result;
    }

    public Long incr(String key) {
        return incrWithExpire(key, 0);
    }

    public Long incrWithExpire(String key, int seconds) {
        Jedis jedis = null;
        Long incr;
        try {
            if (StringUtils.isBlank(key)) {
                incr = null;
                return incr;
            }
            jedis = this.getResource();
            incr = jedis.incr(key);
            if (seconds > 0) {
                jedis.expire(key, seconds);
            }
        } catch (Exception e) {
            logger.error("incr {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
        return incr;
    }

    public Long incrBy(String key, Long integer) {
        Jedis jedis = null;
        Long incrBy;
        try {
            if (StringUtils.isBlank(key)) {
                incrBy = null;
                return incrBy;
            }
            if (integer == null) {
                integer = 0L;
            }
            jedis = this.getResource();
            incrBy = jedis.incrBy(key, integer);
        } catch (Exception e) {
            logger.error("incr {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
        return incrBy;
    }

    public Long incrByWithExpire(String key, Long integer, int seconds) {
        Jedis jedis = null;
        Long incrBy;
        try {
            if (StringUtils.isBlank(key)) {
                incrBy = null;
                return incrBy;
            }
            if (integer == null) {
                integer = 0L;
            }
            jedis = this.getResource();
            incrBy = jedis.incrBy(key, integer);

            if (seconds > 0) {
                jedis.expire(key, seconds);
            }
        } catch (Exception e) {
            logger.error("incr {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
        return incrBy;
    }

    public Long decrBy(String key, Long integer) {
        Jedis jedis = null;
        Long decrBy;
        try {
            if (StringUtils.isBlank(key)) {
                decrBy = null;
                return decrBy;
            }
            if (integer == null) {
                integer = 0L;
            }
            jedis = this.getResource();
            decrBy = jedis.decrBy(key, integer);
        } catch (Exception e) {
            logger.error("incr {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
        return decrBy;
    }

    public Long decr(String key) {
        Jedis jedis = null;
        Long decr;
        try {
            if (StringUtils.isBlank(key)) {
                decr = null;
                return decr;
            }
            jedis = this.getResource();
            decr = jedis.decr(key);
            return decr;
        } catch (Exception e) {
            logger.error("decr {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
    }

    /**
     * hgetAll
     *
     * @param key
     * @return
     */
    public Map<String, String> hgetAll(String key) {
        Map<String, String> map = new HashMap<>();
        Jedis jedis = null;
        try {
            if (StringUtils.isBlank(key)) {
                return map;
            }
            jedis = this.getResource();
            map = jedis.hgetAll(key);
            return map;
        } catch (Exception e) {
            logger.error("hgetAll {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
    }

    /**
     * hmset
     *
     * @param key
     * @return
     */
    public String hmset(String key, Map<String, String> map) {
        Jedis jedis = null;
        try {
            if (StringUtils.isBlank(key)) {
                return null;
            }
            jedis = this.getResource();
            return jedis.hmset(key, map);
        } catch (Exception e) {
            logger.error("hmset {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
    }

    /**
     * hdel
     *
     * @param key
     * @param fields
     * @return
     */
    public Long hdel(String key, String... fields) {
        Jedis jedis = null;
        try {
            jedis = this.getResource();
            return jedis.hdel(key, fields);
        } catch (Exception e) {
            logger.error("hdel {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
    }

    /**
     * llen(获取列表长度)
     *
     * @param key
     * @return
     */
    public Long llen(String key) {
        Jedis jedis = null;
        try {
            jedis = this.getResource();
            return jedis.llen(key);
        } catch (Exception e) {
            logger.error("llen {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
    }

    /**
     * lpushx(将一个值插入到已存在的列表头部)
     *
     * @param key
     * @param value
     * @return
     */
    public Long lpushx(String key, String value) {
        return lpush(key, value);
    }

    /**
     * lpush(将一个或多个值插入到列表头部)
     *
     * @param key
     * @param fields
     * @return
     */
    public Long lpush(String key, String... fields) {
        Jedis jedis = null;
        try {
            jedis = this.getResource();
            return jedis.lpush(key, fields);
        } catch (Exception e) {
            logger.error("lpush {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
    }

    /**
     * rpop(移除列表的最后一个元素,返回值为移除的元素)
     *
     * @param key
     * @return
     */
    public String rpop(String key) {
        Jedis jedis = null;
        try {
            jedis = this.getResource();
            return jedis.rpop(key);
        } catch (Exception e) {
            logger.error("rpop {} ", key, e);
            return null;
        } finally {
            this.release(jedis);
        }
    }


    /**
     * 获取分布式锁
     *
     * @param lockKey
     * @return
     */
    public boolean getLock(String lockKey) {
        return this.getLock(lockKey, "1", 5);
    }

    /**
     * 获取分布式锁
     *
     * @param lockKey
     * @param requestId
     * @param expireTime
     * @return
     */
    public boolean getLock(String lockKey, String requestId, int expireTime) {
        Jedis jedis = null;
        try {
            jedis = this.getResource();
            String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
            if ("OK".equals(result)) {
                return true;
            }
            return false;
        } catch (Exception e) {
            logger.error("getLock {} ", lockKey, e);
            return false;
        } finally {
            this.release(jedis);
        }
    }

    /**
     * 释放分布式锁
     *
     * @param lockKey
     * @return
     */
    public boolean unLock(String lockKey) {
        return this.unLock(lockKey, "1");
    }

    /**
     * 释放分布式锁
     *
     * @param lockKey
     * @param requestId
     * @return
     */
    public boolean unLock(String lockKey, String requestId) {
        Jedis jedis = null;
        try {
            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                    "    return redis.call(\"del\",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end";
            jedis = this.getResource();
            Object result = jedis.eval(script, Collections.singletonList(lockKey),
                    Collections.singletonList(requestId));
            if ("OK".equals(result)) {
                return true;
            }
            return false;
        } catch (Exception e) {
            logger.error("unLock {} ", lockKey, e);
            return false;
        } finally {
            this.release(jedis);
        }
    }

    private Jedis getResource() throws Exception {
        try {
            return jedisPool.getResource();
        } catch (Exception e) {
            logger.error("getResource.", e);
            throw e;
        }
    }

    private void release(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
}

11、下面就是两个关键的类了

ShiroConfiguration

package com.gane.maple.member.config.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

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

/**
 * @Description TODO
 * @Date 2020/4/9 19:58
 * @Created by 王弘博
 */
@Configuration
public class ShiroConfiguration {

    @Value("${jedis.pool.host}")
    private String host;
    @Value("${jedis.pool.port}")
    private int port;
    @Value("${jedis.pool.password}")
    private String password;
    @Value("${jedis.pool.database}")
    private int database;

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
        defaultSecurityManager.setRealm(userRealm());
        defaultSecurityManager.setCacheManager(cacheManager());
        defaultSecurityManager.setSessionManager(sessionManager());
        defaultSecurityManager.setRememberMeManager(rememberMeManager());
        return defaultSecurityManager;
    }

    @Bean
    public UserShiroRealm userRealm() {
        UserShiroRealm userShiroRealm = new UserShiroRealm();
        userShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        userShiroRealm.setCachingEnabled(false);
        return userShiroRealm;
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/user");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        //anon为不用登陆就可以访问的,authc 是必须登陆才可以访问, 
        //filterChainDefinitionMap.put("/**", "authc") 这个一定要放在最后
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/logout", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/index", "anon");
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 给static,不然Redis注入不进来
     *
     * @return
     */
    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
     * 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和
     * AuthorizationAttributeSourceAdvisor)即可实现此功能
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 用户注册的时候,程序将明文通过加密方式加密,存到数据库的是密文,
     * 登录时将密文取出来,再通过shiro将用户输入的密码进行加密对比,一样则成功,不一样则失败。
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用md5算法;
        hashedCredentialsMatcher.setHashIterations(1024);//散列的次数,比如散列两次,相当于 md5( md5(""));
        return hashedCredentialsMatcher;
    }

    /**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setPassword(password);
        redisManager.setDatabase(database);
        return redisManager;
    }

    /**
     * Session Manager
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * 记住我
     *
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        //rememberme cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位),通过以下代码可以获取
        //KeyGenerator keygen = KeyGenerator.getInstance("AES");
        //SecretKey deskey = keygen.generateKey();
        //System.out.println(Base64.encodeToString(deskey.getEncoded()));
        byte[] cipherKey = Base64.decode("wGiHplamyXlVB11UXWol8g==");
        cookieRememberMeManager.setCipherKey(cipherKey);
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }

    @Bean
    public SimpleCookie rememberMeCookie() {
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //如果httyOnly设置为true,则客户端不会暴露给客户端脚本代码,使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击;
        simpleCookie.setHttpOnly(true);
        //记住我cookie生效时间,默认30天 ,单位秒:60 * 60 * 24 * 30
        simpleCookie.setMaxAge(60 * 60 * 7);
        return simpleCookie;
    }
}

UserShiroReaml

package com.gane.maple.member.config.shiro;

import com.gane.maple.member.model.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

/**
 * @Description UserShiroRealm
 * @Date 2020/4/9 19:59
 * @Created by 王弘博
 */
public class UserShiroRealm extends AuthorizingRealm {

    /**
     * 权限授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //表明当前登录者的角色(真实项目中这里会去查询DB,拿到用户的角色,存到redis里)
        info.addRole("admin");

        //表明当前登录者的角色(真实项目中这里会去查询DB,拿到该角色的资源权限,存到redis里)
        info.addStringPermission("admin:manage");

        return info;
    }

    /**
     * 登录认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        //通过token去查询DB,获取用户的密码,这里密码直接写死
        User user = new User();
        user.setUsername(token.getUsername());

        return new SimpleAuthenticationInfo(user, "26bfdfe8689183e9235b1f0beb7a6f46",
                ByteSource.Util.bytes(user.getUsername()), getName());
    }


    /**
     * 密码(123456) + salt(maple),得出存进数据库里的密码:26bfdfe8689183e9235b1f0beb7a6f46
     *
     * @param args
     */
    public static void main(String[] args) {
        String hashAlgorithName = "MD5";
        String password = "123456";
        int hashIterations = 1024;//加密次数
        ByteSource credentialsSalt = ByteSource.Util.bytes("maple");
        SimpleHash simpleHash = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations);
        String s = simpleHash.toHex();
        System.out.println(s);
    }

}

测试:

index.html:不需要登录即可访问

user.html:需要用户登录才可访问,如果用户没有登录,则跳到登录页面 login.html

user_manage.html:有admin:manage权限的用户,才能访问的页面

user_query.html:有admin:query权限的用户,才能访问的页面

(1)、启动项目,直接访问 http://localhost:8080/index,应该可以直接访问

(2)、直接访问 http://localhost:8080/user,应该是跳转到登录页面

(3)、我们输入 maple/111111,密码错误,应该还是留在登录页面

(4)、我们输入 maple/123456,登录成功,应该是跳转到 user.html 页面

(5)、我们访问 http://localhost:8080/query,由于maple用户没有该 权限,所以应该报错(因为我这里没有做异常处理)

 (6)、我们访问 http://localhost:8080/manage,由于maple用户有该 权限,所以应该跳转到 user_manage.html 页面

测试完成,符号预期

发布了165 篇原创文章 · 获赞 103 · 访问量 39万+

猜你喜欢

转载自blog.csdn.net/qq_33101675/article/details/105440491