zoukankan      html  css  js  c++  java
  • spring boot shiro redis整合基于角色和权限的安全管理-Java编程

    一、概述

      本博客主要讲解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


    可以看到返回的是用户未登录提示,也就是我们在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也对应的访问成功了。

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

    至此spring boot shiro redis的整合角色和权限控制讲解完毕。
  • 相关阅读:
    超棒的监控工具 DataDog Splunk 日志易
    API 接口设计 原则
    程序员 架构师 成长 设计 原则
    OAM 继续演进:阿里云携手微软与 Crossplane 社区发布 OAM Kubernetes 标准实现与核心依赖库
    首席架构师 码农总结 互联网整体解决方案
    《不抱怨的世界2》 读后感
    适合开发者的最佳Linux发行版
    大数据 消息 日志
    CRM 线索来源 获客方式
    微服务开发过程中需要注意的若干事项_逍遥子曰
  • 原文地址:https://www.cnblogs.com/telwanggs/p/10809633.html
Copyright © 2011-2022 走看看