最近两个月诸事不顺,有时候显得浮躁焦虑,也没处理好自己的情绪,小伙子,需要冷静。你还有3个月才二十四岁,你着什么急。只有你足够优秀,才能遇到更优秀的人。文字是让人静下来的好东西。2019年的第一篇博客就献给Shiro吧。
Apache Shiro是一个Java安全框架,用来做身份验证(用户登录)、授权(权限控制)、密码和会话管理。常用的就是前两个模块。Shiro配置简单,使用起来无倾入性。比SpringSecurity更轻量级。
先对3个核心组件类有个印象,一定先理解这三个东西,对后面写代码和设计有帮助:
本文只是介绍Shiro的基本使用和一般系统的登录,权限模块设计思路。
一步一步看着来,不要着急。慢慢理解,我应该写的很详细了,不难懂。(这里只列核心代码)
pom.xml Shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
本文只做两个事,把用户登录的验证和用户是否拥有某个接口的请求权限交给Shiro来处理。
try{
//省略了其他的常规代码,比如判断字段是否为空之类的
//此Subject就是开头提到的 代表当前用户
Subject subject = SecurityUtils.getSubject();
//用请求的用户名和密码创建UsernamePasswordToken(此类来自shiro包下)
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
//调用subject.login进行验证,验证不通过则会抛出AuthenticationException异常,然后自定义返回信息
subject.login(usernamePasswordToken);
//未抛异常 则验证通过
//此Session也来自shiro包 是对传统的HttpSession的封装,可以看做是一样的
Session session = subject.getSession();
//下面的是自定义的代码,随你怎么写
session.setAttribute(RequestConstans.USER_ID, checkUser.getId());
} catch (AuthenticationException e) {
//这行也是自定义的代码,随你怎么写
throw new BusinessException(BusinessErrorCode.USER_LOGIN_PWD_ERROR_FAIL);
}
这是截取的登录接口的一部分代码,这就是登录接口方法的处理了,很简单,通过subject.login(usernamePasswordToken);方法,就把登录验证交给Shiro来处理了。省略了原来自己去判断用户名跟密码是否匹配的过程。
扩展:hiro session和Spring session一样吗?
package com.web.config;
import com.mechat.backend.dao.CommercialMapper;
import com.mechat.backend.dao.SystemMenuMapper;
import com.mechat.backend.model.Commercial;
import com.mechat.backend.model.SystemMenu;
import com.mechat.backend.utils.UserConfig;
import com.mechat.common.auth.AESUtils;
import org.apache.shiro.authc.*;
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.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.stream.Collectors;
/** * @Author: WangRui * @Date: 2019/1/22 * Time: 22:43 * Description: */
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private SystemMenuMapper systemMenuMapper;
@Autowired
private UserMapper userMapper;
/** * 用户登录认证方法 * 上面不是调用了subject.login()方法嘛,就会进入到这个方法来进行具体登录验证 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//authenticationToken.getPrincipal()是获得用户名
if (authenticationToken.getPrincipal() == null) {
throw new AuthenticationException("账号名为空,登录失败!");
}
//获取用户信息
String name = authenticationToken.getPrincipal().toString();
User user = new User();
user.setUserName(name);
//通过用户名在数据库查到该用户的信息
user = userMapper.selectByPKey(user);
if (user == null) {
//这里返回后会报出对应异常
throw new AuthenticationException("不存在的账号,登录失败!");
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
//getName() 是Shiro包下org.apache.shiro.realm.CachingRealm的方法,不是自定义的
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword, getName());
return simpleAuthenticationInfo;
}
}
/** * 用户鉴权 * 用户请求有权限要求的接口时要经过此认证, * 譬如我在某个Controller的方法上加了注解@RequiresPermissions(value = "permis[get]"), * 那么该用户的角色拥有的资源必须要包含“permis[get]”权限才能访问此接口, * “permis[get]”对应文章开头说的表结构那里的系统资源表中的permission_code * * @param principalCollection * @return */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户名,此用户名是在登录接口里new UsernamePasswordToken()时设置的
String account = (String) principalCollection.getPrimaryPrincipal();
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//根据用户账号查询拥有的所有资源权限 SystemMenu对应表system_resources,这是一条关联sql,
//通过用户账号-拿到角色id-在role_resources查询到拥有的resources_id-然后关联system_resources查询到拥有的所有资源
List<SystemMenu> systemMenuList = systemMenuMapper.findUserPermission(account);
//添加角色 因为此次只关联到权限permission,故暂不添加角色,只通过permission来鉴权
//添加角色对应的使用注解是 @RequiresRoles()
//simpleAuthorizationInfo.save("admin");
//添加权限 在这里就把该用户对应的角色拥有的所有的权限的permission_code添加到Shiro了,每次访问带有权限限制的接口时就会验证,拥有对应权限code的话就可以正常访问。
simpleAuthorizationInfo.addStringPermissions(systemMenuList.stream().map(systemMenu -> systemMenu.getPermission()).collect(Collectors.toList()));
return simpleAuthorizationInfo;
}
}
注解使用图解:
注意@RequiresPermissions(value = “permis[get]”) value的值就是对应资源的permission_code,见开头表结构设计那里!!!换句话说,此用户必须要有此资源权限才能访问这个Controller方法。
自定义的MyShiroRealm写好了,要配置他才能使用。下面就是Shiro的配置类了:
定义了一个Properties,需要忽略验证的请求路径
@Component
@ConfigurationProperties
public class IgnoreAuthUrlProperties {
List<String> ignoreAuthUrl;
public List<String> getIgnoreAuthUrl() {
return ignoreAuthUrl;
}
public void setIgnoreAuthUrl(List<String> ignoreAuthUrl) {
this.ignoreAuthUrl = ignoreAuthUrl;
}
}
package com.web.config;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.*;
/** * @Author: WangRui * @Date: 2019/1/22 * Time: 22:44 * Description: */
@Configuration
public class ShiroConfiguration {
@Autowired
private IgnoreAuthUrlProperties ignoreAuthUrlProperties;
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
@Bean
public CacheManager cacheManager() {
return new EhCacheManager();
}
@Bean
public SessionDAO sessionDAO() {
return new EnterpriseCacheSessionDAO();
}
@Bean
public SessionManager sessionManager(SessionDAO sessionDAO) {
DefaultWebSessionManager manager = new DefaultWebSessionManager();
manager.setSessionDAO(sessionDAO);
//设置session过期时间
manager.setGlobalSessionTimeout(3600000);
manager.setSessionValidationInterval(3600000);
return manager;
}
/** * 权限管理,配置主要是Realm的管理认证 */
@Bean
public SecurityManager securityManager(CacheManager cacheManager, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager);
securityManager.setRealm(myShiroRealm());
securityManager.setCacheManager(cacheManager);
return securityManager;
}
/** * Filter工厂,设置对应的过滤条件和跳转条件 这是重点!!! */
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Set<String> urlSet = new HashSet<>(ignoreAuthUrlProperties.getIgnoreAuthUrl());
//必须采用LinkedHashMap有序存储过滤条件
Map<String, String> map = new LinkedHashMap<>();
//anon表示所有用户都可以不鉴权匿名访问
urlSet.stream().forEach(temp -> map.put(temp, "anon"));
//此路径必须放在最后 这是为啥一定使用LinkedHashMap的原因
map.put("/**", "authc");
//登录
shiroFilterFactoryBean.setLoginUrl("/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/** * 加入shiro注解的使用,不加入这个注解不生效 */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
至次,Shiro的登录验证和鉴权算是开发完成了。只是简单实用,Shiro还有其他很强大的功能。
有啥疑问网上搜一搜应该都能解决。也可以留言。
我的: