主要功能:认证,授权,加密,会话管理,web集成,缓存等。
三个核心组件:Subject, SecurityManager 和 Realms.
Subject:当前操作用户。
SecurityManager:Shiro框架核心,管理内部组件实例,提供安全管理的各种服务。
Realm:Shiro从应用配置的Realm中查找用户及其权限信息。
从下到上,从左到右依次配置:
Realm => DefaultWebSecurityManger => ShiroFilterFactoryBean
Shiro常用的方法
// 对账号密码设置Token令牌
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
// 获取当前用户对象 Subject
Subject currentUser = SecurityUtils.getSubject();
// 通过当前用户拿到Session
Session session = currentUser.getSession();
// 判断当前用户是否被认证
currentUser.isAuthenticated();
// 获取当前用户的认证
currentUser.getPrincipal();
// 获取当前用户拥有什么角色
currentUser.hasRole("role");
// 获取当前用户的一些权限
currentUser.isPermitted("power");
// 注销
currentUser.logout();
1、引入依赖
<!--shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.14</version>
</dependency>
<!--thymeleaf模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring+mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
2、ShiroRealm(Ream认证授权)
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private AdminUserRoleService adminUserService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
AdminUserEntity armUser = (AdminUserEntity) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//userId
Long userId = adminUserService.queryByUserCode(armUser.getUserCode()).getId();
AdminUserRoleEntity adminUserRoleEntity = adminUserService.getRoleByUserId(userId);
if (Objects.nonNull(adminUserRoleEntity)) {
List<AdminPermissonEntity> permissionList = adminUserService.getPermissionByRoleId(adminUserRoleEntity.getRoleId());
if(CollectionUtils.isNotEmpty(permissionList)){
for (AdminPermissonEntity permission : permissionList){
if(StringUtils.isNotEmpty(permission.getPermissionName())){
// 设置当前用户的权限(权限从数据库中读取)
simpleAuthorizationInfo.addStringPermission(permission.getPermissionName());
}
}
}
}
return simpleAuthorizationInfo;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String name = token.getUsername();
// 从数据库获取对应用户名密码的用户
AdminUserEntity adminUser = adminUserService.getAdminUserEntity(name);
// 登录用户存在
if (adminUser != null) {
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
adminUser, // 用户
adminUser.getNumber(), // 密码认证,Shiro帮做
getName() //realm name
);
return authenticationInfo;
}
return null;
}
}
3、shiroConfig配置
@Configuration
public class ShiroConfig {
@Value("${spring.application.environment}")
private String env;
@Autowired
private RedisProperties redisProperties;
/**
* 第一步:
* 创建 Realm对象,需要自定义
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
*/
@Bean
public ShiroRealm myShiroRealm() {
ShiroRealm myShiroRealm = new ShiroRealm();
myShiroRealm.setCredentialsMatcher(credentialsMatcher());
return myShiroRealm;
}
/**
* 第二步:
* SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联自定义 Realm
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 第三步:
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
// 设置安全管理器
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 没有登陆的用户只能访问登陆页面,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
shiroFilterFactoryBean.setLoginUrl("/admin/unauth");
shiroFilterFactoryBean.setLoginUrl("/h5/unauth");
// 登录成功后要跳转的链接(前后端分离不需要)
//shiroFilterFactoryBean.setSuccessUrl("/auth/index");
// 未授权界面,前后端分离仅返回json数据
shiroFilterFactoryBean.setUnauthorizedUrl("/admin/unauth");
shiroFilterFactoryBean.setUnauthorizedUrl("/h5/unauth");
// 添加Shiro的内置过滤器
/*
anon:无需认证就可以访问;
authc:必须认证才可以访问;
user:必须拥有“记住我”才能访问;
perms:拥有某个资源的权限才能访问;
role:拥有某个角色权限才能访问。
*/
// 设置过滤器链
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 公共请求
filterChainDefinitionMap.put("/common/**", "anon");
// 静态资源
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/index.html", "anon");
filterChainDefinitionMap.put("/assets/**", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
// 登录接口直接访问
filterChainDefinitionMap.put("/admin/doLogin", "anon");
filterChainDefinitionMap.put("/admin/common-login/Login/dualSystem", "anon");
filterChainDefinitionMap.put("/h5/doLogin", "anon");
filterChainDefinitionMap.put("/h5/auth", "anon");
filterChainDefinitionMap.put("/h5/login/Login/dualSystem", "anon");
filterChainDefinitionMap.put("/admin/**", "authc");
filterChainDefinitionMap.put("/h5/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//自定义过滤器
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("authc", new MyShiroFilter());
shiroFilterFactoryBean.setFilters(filterMap);
return shiroFilterFactoryBean;
}
@Bean
public CredentialsMatcher credentialsMatcher() {
return new CredentialsMatcher();
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
if (BizConstant.DEV.equals(env) || BizConstant.DEFAULT.equals(env)) {
redisCacheManager.setRedisManager(redisManager());
} else {
redisCacheManager.setRedisManager(redisClusterManager());
}
redisCacheManager.setKeyPrefix("SHIRO_CACHE:"); //设置前缀
return redisCacheManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
if (BizConstant.DEV.equals(env) || BizConstant.DEFAULT.equals(env)) {
redisSessionDAO.setRedisManager(redisManager());
} else {
redisSessionDAO.setRedisManager(redisClusterManager());
}
redisSessionDAO.setKeyPrefix("SHIRO_SESSION:");
return redisSessionDAO;
}
/**
* Session Manager
* 使用的是shiro-redis开源插件
*/
@Bean
public SessionManager sessionManager() {
SimpleCookie simpleCookie = new SimpleCookie("Token");
simpleCookie.setPath("/");
simpleCookie.setHttpOnly(false);
SessionManager sessionManager = new SessionManager();
//全局会话超时时间(单位毫秒),默认30分钟
sessionManager.setGlobalSessionTimeout(BizConstant.TIME_OUT * 1000);
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setSessionIdCookieEnabled(false);
sessionManager.setSessionIdUrlRewritingEnabled(false);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionIdCookie(simpleCookie);
return sessionManager;
}
@Bean
@Profile({BizConstant.DEV, BizConstant.DEFAULT})
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(redisProperties.getHost() + ":" + redisProperties.getPort());
redisManager.setTimeout(BizConstant.TIME_OUT); //设置过期时间
redisManager.setPassword(redisProperties.getPassword());
return redisManager;
}
@Bean
@Profile({BizConstant.UAT, BizConstant.PROD})
public RedisClusterManager redisClusterManager() {
List<String> nodes = redisProperties.getNodes();
RedisClusterManager redisClusterManager = new RedisClusterManager();
redisClusterManager.setHost(String.join(BizConstant.COMMA, nodes));
return redisClusterManager;
}
/**
* 限制同一账号登录同时登录人数控制
*
* @return
*/
// @Bean
// public SessionControlFilter kickoutSessionControlFilter() {
// SessionControlFilter kickoutSessionControlFilter = new SessionControlFilter();
// kickoutSessionControlFilter.setCache(cacheManager());
// kickoutSessionControlFilter.setSessionManager(sessionManager());
// kickoutSessionControlFilter.setKickoutAfter(false);
// kickoutSessionControlFilter.setMaxSession(1);
// kickoutSessionControlFilter.setKickoutUrl("/common/kickout");
// return kickoutSessionControlFilter;
// }
/***
* 授权所用配置
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/***
* 使授权注解起作用不如不想配置可以在pom文件中加入
* <dependency>
*<groupId>org.springframework.boot</groupId>
*<artifactId>spring-boot-starter-aop</artifactId>
*</dependency>
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro生命周期处理器
* 此方法需要用static作为修饰词,否则无法通过@Value()注解的方式获取配置文件的值
*/
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
4、SessionManager(Session管理器)
public class SessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Token";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public SessionManager() {
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//获取请求头,或者请求参数中的Token
String id = StringUtils.isEmpty(WebUtils.toHttp(request).getHeader(AUTHORIZATION))
? request.getParameter(AUTHORIZATION) : WebUtils.toHttp(request).getHeader(AUTHORIZATION);
// 如果请求头中有 Token 则其值为sessionId
if (StringUtils.isNotEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
// 否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
/**
* 获取session 优化单次请求需要多次访问redis的问题
*
* @param sessionKey
* @return
* @throws UnknownSessionException
*/
@Override
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = getSessionId(sessionKey);
ServletRequest request = null;
if (sessionKey instanceof WebSessionKey) {
request = ((WebSessionKey) sessionKey).getServletRequest();
}
if (request != null && null != sessionId) {
Object sessionObj = request.getAttribute(sessionId.toString());
if (sessionObj != null) {
return (Session) sessionObj;
}
}
Session session = super.retrieveSession(sessionKey);
if (request != null && null != sessionId) {
request.setAttribute(sessionId.toString(), session);
}
return session;
}
}
5、CredentialsMatcher(加盐加密)
public class CredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken utoken = (UsernamePasswordToken) token;
// 获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
String inPassword = new String(utoken.getPassword());
// 获得数据库中的密码
String dbPassword = (String) info.getCredentials();
// 进行密码的比对
try {
return this.equals(AESUtils.encrypt(inPassword, "", "utf-8"), dbPassword);
}catch (Exception e) {
e.printStackTrace();
}
return false;
}
}