Spring Boot(十二):Shiro登录认证和权限管理

15 篇文章 0 订阅
5 篇文章 0 订阅

前言

这篇文章来学习如何使用 Spring Boot 集成 Apache Shiro 。安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求。在 Java 领域一般有 Spring Security、 Apache Shiro 等安全框架,但是由于 Spring Security 过于庞大和复杂,并且依赖spring环境;而Apache Shiro就相对独立,最主要是因为shiro使用简单、灵活,所以现在越来越多的用户选择Apache Shiro这篇文章会先介绍一下 Apache Shiro ,在结合 Spring Boot 给出使用案例。

Apache Shiro

它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

快速上手

1. 添加相关依赖

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

RBAC

RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。

2. 创建相关系统表

用户信息表:

DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `rid` int(11) NOT NULL DEFAULT '2' COMMENT '角色id',
  `username` varchar(32) NOT NULL COMMENT '用户名',
  `password` varchar(32) NOT NULL COMMENT '密码',
  `salt` varchar(32) NOT NULL COMMENT '盐值',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

角色信息表:     

DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(32) NOT NULL COMMENT '角色名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

权限信息表:

DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(32) NOT NULL COMMENT '权限名称',
  `url` varchar(32) NOT NULL COMMENT '权限url',
  `type` varchar(32) NOT NULL COMMENT '权限类型',
  `percode` varchar(32) DEFAULT NULL COMMENT '权限标识',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

   角色-权限信息表:  

DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
  `rid` int(11) NOT NULL COMMENT '角色id',
  `pid` int(11) NOT NULL COMMENT '权限id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

   用户权限视图:     

select 
    `a`.`id` AS `id`,`p`.`name` AS `name`,`p`.`percode` AS `percode` 
from 
    `account` `a`
join
     `role` `r` 
on
    `a`.`rid` = `r`.`id` 
join 
    `role_permission` `rp` 
on
    `r`.`id` = `rp`.`rid`
join 
    `permission` `p` 
on
    `rp`.`pid` = `p`.`id`

  3. Shiro 配置        

       ShiroConfig.java     

@Configuration
public class ShiroConfig {
	/**
	 * 1. 凭证匹配器
	 * 
	 * @return
	 */
	@Bean("credentialsMatcher")
	public HashedCredentialsMatcher getCredentialsMatcher() {
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		hashedCredentialsMatcher.setHashAlgorithmName("md5");
		hashedCredentialsMatcher.setHashIterations(2);
		return hashedCredentialsMatcher;
	}

	/**
	 * 2. 自定义realm
	 * 
	 * @return
	 */
	@Bean("shiroUserRealm")
	public ShiroUserRealm getShiroUserRealm() {
		ShiroUserRealm shiroUserRealm = new ShiroUserRealm();
		shiroUserRealm.setCredentialsMatcher(getCredentialsMatcher());
		return shiroUserRealm;
	}
	/**
	 * 3.1 设置记住我cookie
	 * @return
	 */
	@Bean
	public SimpleCookie getSimpleCookie() {
		SimpleCookie simpleCookie = new SimpleCookie();
		simpleCookie.setMaxAge(60*60*24*7);
		simpleCookie.setName("rememberMe");
		return simpleCookie;
	}
	/**
	 * 3.2   配置记住我
	 * @return
	 */
	@Bean
	public CookieRememberMeManager getCookieRememberMeManager(){
		CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
		cookieRememberMeManager.setCookie(getSimpleCookie());
		return cookieRememberMeManager;
	}
	
	
	
	@Bean
	public Authorizer authorizer(ShiroUserRealm shiroUserRealm) {
		ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();
		Collection<Realm> crealms = new ArrayList<>();
		crealms.add(shiroUserRealm);
		authorizer.setRealms(crealms);
		return authorizer;
	}
	@Bean
	public Authenticator authenticator(ShiroUserRealm shiroUserRealm) {
		ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
		Collection<Realm> crealms = new ArrayList<>();
		crealms.add(shiroUserRealm);
		authenticator.setRealms(crealms);
		return authenticator;
	}
	/**
	 * 4. securityManager管理器
	 * 
	 * @return
	 */
	@Bean("securityManager")
	public DefaultWebSecurityManager getSecurityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		ShiroUserRealm shiroUserRealm = getShiroUserRealm();
		securityManager.setAuthenticator(authenticator(shiroUserRealm));
		securityManager.setAuthorizer(authorizer(shiroUserRealm));
		securityManager.setRealm(shiroUserRealm);
		securityManager.setRememberMeManager(getCookieRememberMeManager());
		return securityManager;
	}
	
	
	
	
	/**
	 * 5. shiroFilter
	 * @param securityManager
	 * @return
	 */
	@Bean("shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
		shiroFilterFactoryBean.setLoginUrl("/api/login");
		// 登录成功后要跳转的链接
		//shiroFilterFactoryBean.setSuccessUrl("/api/index");
		// 未授权界面;
		shiroFilterFactoryBean.setUnauthorizedUrl("/login.html");
		// 拦截器链
		Map<String, String> map = new LinkedHashMap<String, String>();
		map.put("/toLogin", "anon");
		map.put("/api/login", "authc");
		map.put("/api/logout", "logout");
		map.put("/admin/**", "user");
		map.put("/user/**", "user");
		map.put("/js/**", "anon");
		map.put("/css/**", "anon");
		map.put("/images/**", "anon");
		map.put("/**", "anon");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
		Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
		
		return shiroFilterFactoryBean;
	}

	
	
	
	/**
	 * 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;
	 * 
	 * @param securityManager
	 * @return
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
			DefaultWebSecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

	/**
	 * 开启Shiro的注解
	 * 		(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
	 * 		配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
	 * 
	 * @return
	 */
	@Bean
	public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		advisorAutoProxyCreator.setProxyTargetClass(true);
		return advisorAutoProxyCreator;
	}
	/**
	 * 配置authc,不是必须配置,如果不配置,表单的用户名和密码必须是:username,password
	 * 
	 * @return
	 */
	@Bean("formAuthenticationFilter")
	public FormAuthenticationFilter getFormAuthenticationFilter() {
		FormAuthenticationFilter formAuthenticationFilter = new FormAuthenticationFilter();
		formAuthenticationFilter.setUsernameParam("username");
		formAuthenticationFilter.setPasswordParam("password");
		return formAuthenticationFilter;
	}

}

4. 自定义ShiroUserRealm   

public class ShiroUserRealm extends AuthorizingRealm {
	
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	
	@Autowired
	private AccountService accountService;
	
	@Autowired
	private PermissionService permissionService;
	
	/**
	 * 验证
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		String username = token.getPrincipal().toString();
		logger.info("----------认证-----------------username="+username);
		AccountEntity accountEntity = accountService.getByUsername(username);
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(accountEntity, accountEntity.getPassword(),ByteSource.Util.bytes(accountEntity.getSalt()),getName());
		return simpleAuthenticationInfo;
	}
	/**
	 * 授权
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
		AccountEntity accountEntity = (AccountEntity) principal.getPrimaryPrincipal();
		logger.info("----------凭证-----------------password="+accountEntity.getPassword());
		List<Permission> permissions = permissionService.getByUserId(accountEntity.getId());
		if(null == permissions || permissions.size()==0) {
			return null;
		}
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		for (Permission permission : permissions) {
			String percode = permission.getPercode();
			if(null!=percode) {
				info.addStringPermission(percode);
			}
		}
		return info;
	}

	/**
	 * 清理缓存
	 */
	protected void clearCache() {
		Subject subject = SecurityUtils.getSubject();
		super.clearCache(subject.getPrincipals());
	}
}

5. 登录实现:  

/**
	 *   登录
	 *   
	 * @param req
	 * @return
	 */
	@ResponseBody
	@PostMapping("/api/login")
	public Result login(HttpServletRequest request, String username, String password,String captcha,Boolean rememberMe){
		if(StringUtils.isBlank(username) || StringUtils.isBlank(password)|| StringUtils.isBlank(captcha) ) {
			logger.info("参数错误");
			return Result.fail("参数错误...");
		}
		String captchaCookie = CookieUtil.getCookieByName(request, "shiro_captcha");
		if(StringUtils.isBlank(captchaCookie)) {
			logger.info("没有验证码");
			return Result.fail("没有验证码...");
		}
		Object selCaptcha = CacheUtils.get(captchaCookie);
		CacheUtils.remove(captchaCookie);
		if(null == selCaptcha ) {
			logger.info("验证码失效");
			return Result.fail("验证码失效...");
		}
		if(!captcha.equalsIgnoreCase(selCaptcha.toString())) {
			logger.info("验证码错误");
			return Result.fail("验证码错误...");
		}
		UsernamePasswordToken token = new UsernamePasswordToken(username,password,rememberMe);
		Subject subject = SecurityUtils.getSubject();
		try {
			subject.login(token);
			return Result.success("登录成功");
		} catch (Exception e) {
			String exceptionClassName = (String)request.getAttribute("shiroLoginFailure");
			String msg = "用户名/密码错误";
			if(UnknownAccountException.class.getName().equals(exceptionClassName)) {
				return Result.fail(msg);
			} else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){
				return Result.fail(msg);
			}
			return Result.fail(exceptionClassName);
		}
	}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java的艺术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值