SpringBoot 2.1.3 整合Shiro 1.4

- db
采用RBAC模式,其核心为用户-角色-权限三表。
在这里插入图片描述css

- pom.xmlhtml

首先核心dependency以下(须要AOP依赖):java

<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
</dependency>
<!-- aop依赖 -->
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

可选依赖:web

<!-- shiro-redis -->
<dependency>
			<groupId>org.crazycake</groupId>
			<artifactId>shiro-redis</artifactId>
			<version>3.2.2</version>
</dependency>

- ShiroConfig.javaredis

package com.demo.shiro;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

//import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

@Configuration
public class ShiroConfig {
	
	
	/**
	 * Shiro的Web过滤器Factory 命名:shiroFilter
	 */
	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		// 设置 securityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		// 登陆的 url
		shiroFilterFactoryBean.setLoginUrl("/login");
		// 登陆成功后跳转的 url
		shiroFilterFactoryBean.setSuccessUrl("/index");
		// 未受权 url
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");
		LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
		// 设置免认证 url
		filterChainDefinitionMap.put("/jsonTest", "anon");
		filterChainDefinitionMap.put("/css/**", "anon");
		filterChainDefinitionMap.put("/js/**", "anon");
		filterChainDefinitionMap.put("/photos/**", "anon");
		filterChainDefinitionMap.put("/webjars/**", "anon");
		filterChainDefinitionMap.put("/guest/**", "anon");

		// }
		// 配置退出过滤器,其中具体的退出代码 Shiro已经替咱们实现了
		//filterChainDefinitionMap.put("/logout", "logout");
		// 除上之外全部 url都必须认证经过才能够访问,未经过认证自动访问 LoginUrl
		filterChainDefinitionMap.put("/**", "user");

		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

		return shiroFilterFactoryBean;
	}

	// 注入自定义的realm,告诉shiro如何获取用户信息来作登陆或权限控制
	@Bean
	public CustomRealm realm() {
		return new CustomRealm();
	}

	@Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 配置 SecurityManager,并注入 shiroRealm
		securityManager.setRealm(realm());
		// 配置 rememberMeCookie
		securityManager.setRememberMeManager(rememberMeManager());
		// 配置 缓存管理类 cacheManager
		securityManager.setCacheManager(cacheManager());
		securityManager.setSessionManager(sessionManager());
		return securityManager;
	}

	@Bean(name = "lifecycleBeanPostProcessor")
	public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		// shiro 生命周期处理器
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * DefaultAdvisorAutoProxyCreator 和 AuthorizationAttributeSourceAdvisor 用于开启
	 * shiro 注解的使用 如 @RequiresAuthentication, @RequiresUser, @RequiresPermissions 等
	 *
	 * @return DefaultAdvisorAutoProxyCreator
	 */
	@Bean
	@DependsOn({ "lifecycleBeanPostProcessor" })
	public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		advisorAutoProxyCreator.setProxyTargetClass(true);
		return advisorAutoProxyCreator;
	}

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

	/**
	 * 用于开启 Thymeleaf 中的 shiro 标签的使用
	 *
	 * @return ShiroDialect shiro 方言对象
	 */
//	@Bean
//	public ShiroDialect shiroDialect() {
//		return new ShiroDialect();
//	}

	/**
	 * shiro 中配置 redis 缓存
	 *
	 * @return RedisManager
	 */
	private RedisManager redisManager() {
		RedisManager redisManager = new RedisManager();
		return redisManager;
	}

	/**
	 * shiro 中配置 redis cache缓存
	 *
	 * @return RedisCacheManager
	 */
	private RedisCacheManager cacheManager() {
		RedisCacheManager redisCacheManager = new RedisCacheManager();
		redisCacheManager.setRedisManager(redisManager());
		return redisCacheManager;
	}

	/**
	 * rememberMe cookie 效果是重开浏览器后无需从新登陆
	 *
	 * @return SimpleCookie
	 */
	private SimpleCookie rememberMeCookie() {
		// 设置 cookie 名称,对应 login.html 页面的 <input type="checkbox" name="rememberMe"/>
		SimpleCookie cookie = new SimpleCookie("rememberMe");
		// cookie.setSecure(true); // 只在 https中有效 注释掉 正常
		// 设置 cookie 的过时时间,单位为秒,这里为一天
		cookie.setMaxAge(2000);
		return cookie;
	}

	/**
	 * cookie管理对象
	 *
	 * @return CookieRememberMeManager
	 */
	private CookieRememberMeManager rememberMeManager() {
		CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
		cookieRememberMeManager.setCookie(rememberMeCookie());
		// rememberMe cookie 加密的密钥
		cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
		return cookieRememberMeManager;
	}

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

	/**
	 * session 管理对象
	 *
	 * @return DefaultWebSessionManager
	 */
	@Bean
	public DefaultWebSessionManager sessionManager() {
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		Collection<SessionListener> listeners = new ArrayList<>();
		listeners.add(new ShiroSessionListener());
		// 设置session超时时间,单位为毫秒
		sessionManager.setGlobalSessionTimeout(2000000);
		sessionManager.setSessionListeners(listeners);
		sessionManager.setSessionDAO(redisSessionDAO());
		return sessionManager;
	}
}
  • CustomRealm.java
package com.demo.shiro;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
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 com.alibaba.dubbo.config.annotation.Reference;
import com.demo.entity.Permission;
import com.demo.entity.Role;
import com.demo.entity.User;
import com.demo.service.IPermissionService;
import com.demo.service.IRoleService;
import com.demo.service.IUserService;

public class CustomRealm extends AuthorizingRealm {

	private static final Logger log = LoggerFactory.getLogger(CustomRealm.class);

	@Reference(version = "1.0.0")
	private IUserService iUserService;
	@Reference(version = "1.0.0")
	private IRoleService iRoleService;
	@Reference(version = "1.0.0")
	private IPermissionService iPermissionService;
	//定义如何获取用户的角色和权限的逻辑,给shiro作权限判断
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		log.info("------------权限认证---------");
		User user = (User) getAvailablePrincipal(principals);
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		List<Role> list = this.iRoleService.findUserRole(user.getNickname());
		Set<String> set = new HashSet<String>();
		for (Role r : list) {
			set.add(r.getName());
		}		
		Set<String> perms = new HashSet<String>();
		List<Permission> list1 = this.iPermissionService.findRolePerm(user.getNickname());
		for(Permission p : list1) {
			perms.add(p.getUrl());
		}
		info.setRoles(set);//添加角色集合   @RequireRoles("admin")会到info中寻找 字符串 "admin"
		info.setStringPermissions(perms);// 添加权限集合 @RequiresPermissions("test") 会到info中寻找字符串"test"
		return info;
	}

	// 定义如何获取用户信息的业务逻辑,给shiro作登陆
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
			throws AuthenticationException {
		log.info("------------身份认证方法---------");
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
		String nickname = token.getUsername();
		if (nickname == null) {
			throw new AccountException("Null usernames are not allowed by this realm.");
		}
		User user = this.iUserService.findByNickName(nickname); // 从数据库获取对应用户名密码的用户
		if (user == null) {
			throw new UnknownAccountException("No account found for admin [" + nickname + "]");
		}
		if(user.getStatus() == 0) {
			throw new LockedAccountException("您的帐号被禁止登陆,请联系管理员");
		}
		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPswd(), getName());

		return info;
	}

}

- ShiroSessionListener.javaspring

package com.demo.shiro;

 

import java.util.concurrent.atomic.AtomicInteger;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;

public class ShiroSessionListener implements SessionListener{

	private final AtomicInteger sessionCount = new AtomicInteger(0);
	
	@Override
	public void onStart(Session session) {
		sessionCount.incrementAndGet();
	}

	@Override
	public void onStop(Session session) {
		sessionCount.decrementAndGet();
		
	}

	@Override
	public void onExpiration(Session session) {
		sessionCount.decrementAndGet();
	}
}
  • GlobalExceptionHandler.java(Shiro异常捕获)
package com.demo.handler;

import org.apache.shiro.ShiroException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import com.demo.domain.Codes;
import com.demo.domain.Json;


/**
 * 统一捕捉shiro的异常,返回给前台一个json信息,前台根据这个信息显示对应的提示,或者作页面的跳转。
 */
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    //不知足@RequiresGuest注解时抛出的异常信息
    private static final String GUEST_ONLY = "Attempting to perform a guest-only operation";


    @ExceptionHandler(ShiroException.class)
    @ResponseBody
    public Json handleShiroException(ShiroException e) {
        String eName = e.getClass().getSimpleName();
        log.error("shiro执行出错:{}",eName);
        return new Json(eName, false, Codes.SHIRO_ERR, "鉴权或受权过程出错", null);
    }

    @ExceptionHandler(UnauthenticatedException.class)
    @ResponseBody
    public Json page401(UnauthenticatedException e) {
        String eMsg = e.getMessage();
        if (StringUtils.startsWithIgnoreCase(eMsg,GUEST_ONLY)){
            return new Json("401", false, Codes.UNAUTHEN, "只容许游客访问,若您已登陆,请先退出登陆", null)
                    .data("detail",e.getMessage());
        }else{
            return new Json("401", false, Codes.UNAUTHEN, "用户未登陆", null)
                    .data("detail",e.getMessage());
        }
    }

    @ExceptionHandler(UnauthorizedException.class)
    @ResponseBody
    public Json page403() {
        return new Json("403", false, Codes.UNAUTHZ, "用户没有访问权限", null);
    }

}

- 测试
RequiresPermissions("/share")//表明该类下全部方法的访问须要 登陆用户拥有 权限 “/share”,可类比至方法注解等。
public class ShareController {
…//
}数据库

  • 登陆
@RequestMapping(value = { "/login" }, method = { RequestMethod.POST }, consumes = {
			"application/json" }, produces = "application/json;charset=UTF-8")
	public @ResponseBody Json login(@RequestBody User user) {//Json 为封装好的返回对象,能够本身设置
		log.info("==========================/login==================================");
		String oper = "user login : ";
		JSONObject responseObj = (JSONObject) JSONObject.toJSON(user);
		log.info(oper + responseObj);
		String nickname = responseObj.getString("nickname");
		String pswd = responseObj.getString("pswd");
		if (StringUtils.isEmpty(nickname)) {
			return Json.fail(oper, "用户名不能为空");
		}
		if (StringUtils.isEmpty(pswd)) {
			return Json.fail(oper, "密码不能为空");
		}
		pswd = MD5Utils.encrypt(pswd);// 密码MD5加密
		UsernamePasswordToken token = new UsernamePasswordToken(nickname, pswd);
		try {
			// 登陆
			Subject subject = SecurityUtils.getSubject();
			// 从session取出用户信息
			if (subject != null) {
				subject.logout();
			}
			subject.login(token);// shiro 认证
			this.iUserService.updateLoginTime(nickname);// 更新最近一次登陆时间
			User user0 = (User) SecurityUtils.getSubject().getPrincipal();
			//获取用户角色信息
			List<Role> roles = new ArrayList<Role>();
			List<Role> list = this.iRoleService.findUserRole(user.getNickname());
			for (Role r : list) {
				roles.add(r);
			}		
			//获取用户权限信息
			List<Permission> perms = new ArrayList<Permission>();
			List<Permission> list1 = this.iPermissionService.findRolePerm(user.getNickname());
			for(Permission p : list1) {
				perms.add(p);
			}
			user0.setUserRoles(roles);
			user0.setUserPerms(perms);
			
			return Json.succ(oper).data(user0);

		} catch (UnknownAccountException uae) {
			log.warn("用户账号不正确");
			return Json.fail(oper, "用户账号或密码不正确");

		} catch (IncorrectCredentialsException ice) {
			log.warn("用户密码不正确");
			return Json.fail(oper, "用户账号或密码不正确");

		} catch (LockedAccountException lae) {
			log.warn("用户账号被锁定");
			return Json.fail(oper, "用户账号被锁定不可用");

		} catch (AuthenticationException ae) {
			log.warn("登陆出错");
			return Json.fail(oper, "登陆失败:" + ae.getMessage());
		}

	}
相关文章
相关标签/搜索