一、概述

  本博客主要讲解spring boot整合Apache的shiro框架,实现基于角色的安全访问控制或者基于权限的访问安全控制,其中还使用到分布式缓存redis进行用户认证信息的缓存,减少数据库查询的开销。Apache shiro与spring security的作用几乎一样都是简化了Java程序的权限控制开发。

二、项目

2.1首先是通过eclipse创建一个最新的spring boot项目,并添加以下依赖:

pom.xml
<?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>net.xqlee.project.demo.shiro</groupId>
	<artifactId>demo-springboot-shiro</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>demo-springboot-shiro-hello</name>
	<description>demo-springboot-shiro-hello</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.4.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

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

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- shiro权限控制框架 -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-ehcache</artifactId>
			<version>1.3.2</version>
		</dependency>
		<!--缓存暂时用简单的 https://mvnrepository.com/artifact/org.ehcache/ehcache -->
		<dependency>
			<groupId>org.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>
		<!-- https://mvnrepository.com/artifact/net.sf.json-lib/json-lib -->
		<dependency>
			<groupId>net.sf.json-lib</groupId>
			<artifactId>json-lib</artifactId>
			<version>2.4</version>
			<classifier>jdk15</classifier>
		</dependency>
		<!-- redis缓存 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

2.2配置redis

application.properties文件中添加配置:
####################Redis 配置信息 ##########################
# Redis数据库分片索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=10.1.1.2
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0

#测试redis的缓存日志
logging.level.net.xqlee.project.demo.shiro.config.shiro.cache=DEBUG

Java config的redis配置
RedisConfig
package net.xqlee.project.demo.shiro.config.redis;

import java.lang.reflect.Method;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
@EnableCaching // 继承CachingConfigurerSupport并重写方法,配合该注解实现spring缓存框架的启用
public class RedisConfig extends CachingConfigurerSupport {
	/** 载入通过配置文件配置的连接工场 **/
	@Autowired
	RedisConnectionFactory redisConnectionFactory;

	@SuppressWarnings("rawtypes")
	@Autowired
	RedisTemplate redisTemplate;

	@Bean
	RedisTemplate<String, Object> objRedisTemplate() {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setConnectionFactory(redisConnectionFactory);
		return redisTemplate;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.cache.annotation.CachingConfigurerSupport#
	 * cacheManager()
	 */
	@Bean // 必须添加此注解
	@Override
	public CacheManager cacheManager() {
		RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
		// 设置缓存过期时间
		// redisCacheManager.setDefaultExpiration(60);//秒
		return redisCacheManager;
	}

	/**
	 * 重写缓存的key生成策略,可根据自身业务需要进行自己的配置生成条件
	 * 
	 * @see org.springframework.cache.annotation.CachingConfigurerSupport#
	 * keyGenerator()
	 */
	@Bean // 必须项
	@Override
	public KeyGenerator keyGenerator() {
		return new KeyGenerator() {
			@Override
			public Object generate(Object target, Method method, Object... params) {
				StringBuilder sb = new StringBuilder();
				sb.append(target.getClass().getName());
				sb.append(method.getName());
				for (Object obj : params) {
					sb.append(obj.toString());
				}
				return sb.toString();
			}
		};
	}

}


2.3创建shiro需要的 redis缓存器和缓存管理实现类

首先是缓存器cache的实现
RedisCache.java
package net.xqlee.project.demo.shiro.config.shiro.cache;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * Redis的Shiro缓存对象实现
 * 
 * @author xq
 *
 * @param <K>
 * @param <V>
 */
public class RedisCache<K, V> implements Cache<K, V> {
	private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
	private RedisTemplate<K, V> redisTemplate;
	private final static String PREFIX = "shiro-cache:";
	private String cacheKey;
	private long globExpire = 30;

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public RedisCache(final String name, final RedisTemplate redisTemplate) {
		this.cacheKey = PREFIX + name + ":";
		this.redisTemplate = redisTemplate;
	}

	@Override
	public V get(K key) throws CacheException {
		logger.debug("Shiro从缓存中获取数据KEY值["+key+"]");
		redisTemplate.boundValueOps(getCacheKey(key)).expire(globExpire, TimeUnit.MINUTES);
		return redisTemplate.boundValueOps(getCacheKey(key)).get();
	}

	@Override
	public V put(K key, V value) throws CacheException {
		V old = get(key);
		redisTemplate.boundValueOps(getCacheKey(key)).set(value);
		return old;
	}

	@Override
	public V remove(K key) throws CacheException {
		V old = get(key);
		redisTemplate.delete(getCacheKey(key));
		return old;
	}

	@Override
	public void clear() throws CacheException {
		redisTemplate.delete(keys());

	}

	@Override
	public int size() {
		return keys().size();
	}

	@Override
	public Set<K> keys() {
		return redisTemplate.keys(getCacheKey("*"));
	}

	@Override
	public Collection<V> values() {
		Set<K> set = keys();
		List<V> list = new ArrayList<>();
		for (K s : set) {
			list.add(get(s));
		}
		return list;
	}

	@SuppressWarnings("unchecked")
	private K getCacheKey(Object k) {
		return (K) (this.cacheKey + k);
	}
}
还有缓存管理器:
RedisCacheManager.java
package net.xqlee.project.demo.shiro.config.shiro.cache;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * Redis的Shiro缓存管理器实现
 * 
 * @author xq
 *
 */
public class RedisCacheManager implements CacheManager {
	@Autowired
	private RedisTemplate<String, Object> redisTemplate;

	@Override
	public <K, V> Cache<K, V> getCache(String name) throws CacheException {
		return new RedisCache<>(name, redisTemplate);
	}

}

2.4自定义一个shiro的realm实现

创建realm之前需要编写一个模拟数据库查询的用户业务处理类,提供给上面的自定义realm使用

简单的用户登录对象:
package net.xqlee.project.demo.shiro.pojo;

import java.util.Date;
import java.util.List;

/**
 * 用户信息
 * 
 * @author xqlee
 *
 */
public class LoginAccount {
	/** 用户名 */
	String loginName;
	List<String> roles;// 测试用
	List<String> permissions;// 测试用直接放用户登录对象里面
	/** 用户密码 **/
	String password;
	boolean enabled;
	Date createDate;
	boolean isExpired;

	public String getLoginName() {
		return loginName;
	}

	public void setLoginName(String loginName) {
		this.loginName = loginName;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public Date getCreateDate() {
		return createDate;
	}

	public void setCreateDate(Date createDate) {
		this.createDate = createDate;
	}

	public boolean isExpired() {
		return isExpired;
	}

	public void setExpired(boolean isExpired) {
		this.isExpired = isExpired;
	}

	public List<String> getRoles() {
		return roles;
	}

	public void setRoles(List<String> roles) {
		this.roles = roles;
	}

	public List<String> getPermissions() {
		return permissions;
	}

	public void setPermissions(List<String> permissions) {
		this.permissions = permissions;
	}

}
用户模拟业务处理
package net.xqlee.project.demo.shiro.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Component;

import net.xqlee.project.demo.shiro.pojo.LoginAccount;

/**
 * 用户业务服务类
 * 
 * @author xqlee
 *
 */
@Component("userService")
public class UserService {
	/** 由于重点不在数据库,这里需要使用数据库的地方全部用map代替 **/
	/** 用户信息 **/
	static Map<String, LoginAccount> users = new HashMap<>();

	static {
		// 创建一个用户
		LoginAccount account = new LoginAccount();
		account.setLoginName("leftso");
		account.setPassword("123456");
		account.setEnabled(true);
		account.setExpired(false);

		// 角色添加
		List<String> roles = new ArrayList<>();
		roles.add("ROLE_USER");
		account.setRoles(roles);

		List<String> permissions = new ArrayList<>();
		permissions.add("query");
		permissions.add("delete");
		
		account.setPermissions(permissions);
		users.put(account.getLoginName(), account);

		// 创建一个用户
		LoginAccount admin = new LoginAccount();
		admin.setLoginName("admin");
		admin.setPassword("123456");
		admin.setEnabled(true);
		admin.setExpired(false);

		// 角色添加
		roles = new ArrayList<>();
		roles.add("ROLE_ADMIN");
		admin.setRoles(roles);

		permissions = new ArrayList<>();
		permissions.add("query");
		permissions.add("delete");

		admin.setPermissions(permissions);

		users.put("admin", admin);

	}

	/**
	 * 通过用户名获取用户权限集合
	 * 
	 * @param loginName
	 *            用户名
	 * @return 用户的权限集合
	 */
	public List<String> getPermissionsByLoginName(String loginName) {

		if (users.containsKey(loginName)) {
			return users.get(loginName).getPermissions();
		}
		return new ArrayList<>();

	}

	/**
	 * 通过用户名获取用户信息
	 * 
	 * @param loginName
	 *            用户名
	 * @return 用户信息
	 */
	public LoginAccount getLoginAccountByLoginName(String loginName) {
		if (users.containsKey(loginName)) {
			return users.get(loginName);
		}
		return null;

	}
}
 
package net.xqlee.project.demo.shiro.config.shiro;

import java.util.List;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import net.xqlee.project.demo.shiro.pojo.LoginAccount;
import net.xqlee.project.demo.shiro.service.UserService;

/**
 * 实现一个基于JDBC的Realm,继承AuthorizingRealm可以看见需要重写两个方法,doGetAuthorizationInfo和doGetAuthenticationInfo
 * 
 * @author xqlee
 *
 */
@Component("JDBCShiroRealm")
public class JDBCShiroRealm extends AuthorizingRealm {
	private static final Logger logger = LoggerFactory.getLogger(JDBCShiroRealm.class);

	/*** 用户业务处理类,用来查询数据库中用户相关信息 ***/
	@Autowired
	UserService userService;

	/***
	 * 获取用户授权
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		logger.info("##################执行Shiro权限认证##################");
		// 获取用户名
		String loginName = (String) principalCollection.fromRealm(getName()).iterator().next();
		// 判断用户名是否存在
		if (StringUtils.isEmpty(loginName)) {
			return null;
		}
		// 查询登录用户信息
		LoginAccount account = userService.getLoginAccountByLoginName(loginName);
		if (account == null) {
			logger.warn("用户[" + loginName + "]信息不存在");
			return null;
		}
		// 创建一个授权对象
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

		// 进行权限设置
		List<String> permissions = account.getPermissions();
		if (permissions != null && !permissions.isEmpty()) {
			info.addStringPermissions(permissions);
		}
		// 角色设置
		List<String> roles = account.getRoles();
		if (roles != null) {
			info.addRoles(roles);
		}

		return info;
	}

	/**
	 * 获取用户认证信息
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
			throws AuthenticationException {
		logger.info("##################执行Shiro登陆认证##################");
		UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
		// 通过表单接收的用户名
		String loginName = token.getUsername();
		if (loginName != null && !"".equals(loginName)) {
			// 模拟数据库查询用户信息
			LoginAccount account = userService.getLoginAccountByLoginName(loginName);

			if (account != null) {
				// 登陆的主要信息: 可以是一个实体类的对象, 但该实体类的对象一定是根据 token 的 username 查询得到的.
				Object principal = token.getPrincipal();
				// 创建shiro的用户认证对象
				// 注意该对象的密码将会传递至后续步骤与前面登陆的subject的密码进行比对。
				SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal,
						account.getPassword(), getName());

				return authenticationInfo;
			}
		}
		return null;
	}

}



2.5shiro的核心配置文件

ShiroConfig.java
package net.xqlee.project.demo.shiro.config.shiro;

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

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import net.xqlee.project.demo.shiro.config.shiro.cache.RedisCacheManager;

/***
 * shiro权限管理配置
 * 
 * @author xqlee
 *
 */
@Configuration
public class ShiroConfig {
	/**
	 * ehcache缓存方案<br/>
	 * 简单的缓存,后续可更换为redis缓存,通过自己实现shiro的CacheManager接口和Cache接口
	 * 
	 * @return
	 */
	@Bean
	public CacheManager shiroEhCacheManager() {
		EhCacheManager cacheManager = new EhCacheManager();
		cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
		return cacheManager;
	}

	/**
	 * redis缓存方案
	 * 
	 * @return
	 */
	@Bean
	public CacheManager shiroRedisCacheManager() {
		return new RedisCacheManager();
	}

	/****
	 * 自定义Real
	 * 
	 * @return
	 */
	@Bean
	public JDBCShiroRealm jdbcShiroRealm() {
		JDBCShiroRealm realm = new JDBCShiroRealm();
		// 根据情况使用缓存器
		realm.setCacheManager(shiroRedisCacheManager());//shiroEhCacheManager()
		return realm;
	}

	/***
	 * 安全管理配置
	 * 
	 * @return
	 */
	@Bean
	public SecurityManager defaultWebSecurityManager() {
		// DefaultSecurityManager defaultSecurityManager = new
		// DefaultSecurityManager();
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 注意:!!!初始化成这个将会报错java.lang.IllegalArgumentException:
		// SessionContext must be an HTTP compatible
		// implementation.:模块化本地测试shiro的一些总结
		// 配置
		securityManager.setRealm(jdbcShiroRealm());
		// 注意这里必须配置securityManager
		SecurityUtils.setSecurityManager(securityManager);
		// 根据情况选择缓存器
		securityManager.setCacheManager(shiroRedisCacheManager());//shiroEhCacheManager()

		return securityManager;
	}

	/**
	 * 配置shiro的拦截器链工厂,默认会拦截所有请求,并且不可配置
	 * 
	 * @return
	 */
	@Bean
	public ShiroFilterFactoryBean shiroFilterFactoryBean() {
		ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
		// 配置安全管理(必须)
		filterFactoryBean.setSecurityManager(defaultWebSecurityManager());
		// 配置登陆的地址
		filterFactoryBean.setLoginUrl("/userNoLogin.do");// 未登录时候跳转URL,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
		filterFactoryBean.setSuccessUrl("/welcome.do");// 成功后欢迎页面
		filterFactoryBean.setUnauthorizedUrl("/403.do");// 未认证页面

		// 配置拦截地址和拦截器
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 必须使用LinkedHashMap,因为拦截有先后顺序
		// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问

		filterChainDefinitionMap.put("/userNoLogin.do*", "anon");// 未登录跳转页面不设权限认证
		filterChainDefinitionMap.put("/login.do*", "anon");// 登录接口不设置权限认真
		filterChainDefinitionMap.put("/logout.do*", "anon");// 登出不需要认证

		// 以下配置同样可以通过注解
		// @RequiresPermissions("user:edit")来配置访问权限和角色注解@RequiresRoles(value={"ROLE_USER"})方式定义
		// 权限配置示例,这里的配置理论来自数据库查询
		filterChainDefinitionMap.put("/user/**", "roles[ROLE_USER],perms[query]");// /user/下面的需要ROLE_USER角色或者query权限才能访问
		filterChainDefinitionMap.put("/admin/**", "perms[ROLE_ADMIN]");// /admin/下面的所有需要ROLE_ADMIN的角色才能访问

		// 剩下的其他资源地址全部需要用户认证后才能访问
		filterChainDefinitionMap.put("/**", "authc");
		filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

		// 全部配置
		// anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名访问
		//
		// authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
		// 需要登录,不需要权限和角色可访问
		//
		// authcBasic
		// org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
		//
		// perms
		// org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
		// 需要给定的权限值才能访问
		//
		// port org.apache.shiro.web.filter.authz.PortFilter
		//
		// rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
		//
		// roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
		// 需要给定的角色才能访问
		//
		// ssl org.apache.shiro.web.filter.authz.SslFilter
		//
		// user org.apache.shiro.web.filter.authc.UserFilter
		//
		// logout org.apache.shiro.web.filter.authc.LogoutFilter
		return filterFactoryBean;
	}

}
上面配置中有两个缓存器可以选择,一个是简单的ehcache,一个是redis,大型项目推荐使用redis


2.6编写一个测试的controller

package net.xqlee.project.demo.shiro.controller;

import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import net.sf.json.JSONObject;

/**
 * 用户登录用
 * 
 * @author xqlee
 *
 */
@RestController
public class LoginController {
	private static final Logger logger = LoggerFactory.getLogger(LoginController.class);

	/****
	 * 用户未登录
	 * 
	 * @return
	 */
	@GetMapping("userNoLogin.do")
	public Object noLogin() {
		JSONObject object = new JSONObject();
		object.put("message", "用户未登录");
		return object;
	}

	@GetMapping(value = "/login.do")
	public String login(String loginName, String password) {
		try {
			// 创建shiro需要的token
			UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginName, password.toCharArray());
			usernamePasswordToken.setRememberMe(true);// 记住

			try {
				SecurityUtils.getSubject().login(usernamePasswordToken);
			} catch (UnknownAccountException uae) {
				logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,未知账户");
				return "对用户[" + loginName + "]进行登录验证..验证未通过,未知账户";
			} catch (IncorrectCredentialsException ice) {
				logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,错误的凭证");
				ice.printStackTrace();
				return "对用户[" + loginName + "]进行登录验证..验证未通过,错误的凭证";
			} catch (LockedAccountException lae) {
				logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,账户已锁定");
				return "对用户[" + loginName + "]进行登录验证..验证未通过,账户已锁定";
			} catch (ExcessiveAttemptsException eae) {
				logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,错误次数过多");
				return "对用户[" + loginName + "]进行登录验证..验证未通过,错误次数过多";
			} catch (AuthenticationException ae) {
				// 通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
				logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,堆栈轨迹如下");
				ae.printStackTrace();
				return "用户名或密码不正确";
			}
			return "Login Success!";
		} catch (Exception e) {
			return "登陆时候发生异常," + e.getMessage();
		}
	}

	@GetMapping("/user/hello.do")
	public String hello() {
		return "Hello User, From Server";
	}

	@GetMapping("/admin/hello.do")
	public String helloAdmin() {
		return "Hello Admin, From Server";
	}

	@GetMapping("/welcome.do")
	public String loginSuccess() {
		return "welcome";
	}

	@GetMapping("/403.do")
	public Object error403(HttpServletResponse response) {
		response.setStatus(403);
		JSONObject object = new JSONObject();
		object.put("message", "用户权限不够");
		return object;
	}

}

2.7测试

1.通过spring bootapplication方式启动项目,项目默认监听在8080端口

首先访问需要权限的url  http://localhost:8080/user/hello.do

访问需要权限的url
可以看到返回的是用户未登录提示,也就是我们在controller中写的未登录时候调用的方法返回值。

现在我们先通过用户名leftso和密码123456进行登录  localhost:8080/login.do?loginName=leftso&password=123456
登录

可以看到登录成功,这个时候我们再次打开最初访问的:http://localhost:8080/user/hello.do
成功访问
可以看到这次我们成功访问了需要ROLE_USER角色的url,

现在用这个登录的信息访问需要ROLE_ADMIN权限的地址http://localhost:8080/admin/hello.do

提示权限不足
上面可以看到,无法访问,返回的提示是权限不足

切换为admin的用户登录,然后访问地址http://localhost:8080/admin/hello.do

首先是admin登录
admin登录
然后访问admin权限的地址

admin资源访问成功
上面看到admin也对应的访问成功了。

并且,切回eclipse的控制台可以看到:
redis
我们的redis缓存也起作用了。

至此spring boot shiro redis的整合角色和权限控制讲解完毕。

留下思考:如何实现spring boot shiro的无状态认证呢?也就是当我们编写一个既要web前后端分离使用也同时需要满足app的接口需求。只能通过无状态的token实现。
暂无评论