概念模型
用户与角色之间是一对一关联,角色与资源之间是多对多关联(关联关系用中间表来维护)
Resource里面存储着系统的url路径
模型字段都很简单,SQL及Model类就不再贴出来了.
1.引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
添加这一个依赖就够用了,它会依赖core和web
我写的shiro类有这些
CheckLoginFilter 登录认证过滤器
CheckPermissionsFilter 权限校验过滤器
ShiroConfig 配置类(核心)
ShiroLifecycleBeanPostProcessorConfig shiro生命周期管理
ShiroPermissionResolver 生成Permission实例(关键)
ShiroWildcardPermission Permission的子孙类,判断权限通过与否(关键)
UserRealm 提供身份认证和授权(核心)
要使用自定义的鉴权方式,需要写 PermissionResolver 和 WildcardPermission 的子类
注:所有的Service类.就不贴出来了,功能很简单
1.首先看一下UserRealm
doGetAuthorizationInfo 和 doGetAuthenticationInfo方法必须要求实现
这里为了url权限校验的需要,覆盖了isPermitted方法(这不是必须的)
package com.web.shiro;
import com.web.constant.Constant;
import com.web.constant.ErrorCode;
import com.web.dto.AuthorizationRoleInfoDTO;
import com.web.entity.Role;
import com.web.entity.User;
import com.web.exception.PolarisException;
import com.web.service.RoleService;
import com.web.service.UserService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shiro.SecurityUtils;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
/**
* Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”
*
* @author
* @date 2019/07/09 10:43
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
@Lazy
private UserService userService;
@Autowired
@Lazy
private RoleService roleService;
@Autowired
@Lazy
private MemorySessionDAO sessionDAO;
/**
* 授权
*
* @param principal principal
* @return 用户权限信息集合
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
User user = (User) principal.getPrimaryPrincipal();
SimpleAuthorizationInfo info = (SimpleAuthorizationInfo) SecurityUtils.getSubject()
.getSession().getAttribute(Constant.AUTHORIZATION_INFO);
if (info != null) {
return info;
}
info = new SimpleAuthorizationInfo();
AuthorizationRoleInfoDTO roleInfo = roleService.getAuthorizationRoleInfoDTOByUsername(user.getUsername());
if (roleInfo == null) {
return info;
}
info.addRole(String.valueOf(roleInfo.getRoleId()));
// 设置权限码
info.setStringPermissions(new HashSet<>(roleInfo.getUrls()));
SecurityUtils.getSubject().getSession().setAttribute(Constant.AUTHORIZATION_INFO, info);
return info;
}
/**
* 身份认证
*
* @param token token
* @return 用户角色信息集合
* @throws AuthenticationException 认证异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 当事人,类型取决于调用subject.login之前存储的数据类型
String username = (String) token.getPrincipal();
User user = userService.getByUsername(username);
if (null == user) {
throw new PolarisException(ErrorCode.USERNAME_OR_PASSWORD_ERROR);
}
if (User.STATUS_DISABLE == user.getStatus()) {
throw new PolarisException(ErrorCode.USER_IS_DISABLED);
}
if (User.STATUS_DELETED == user.getStatus()) {
throw new PolarisException(ErrorCode.USERNAME_OR_PASSWORD_ERROR);
}
String password = user.getPassword();
user.setPassword(null);
user.setRole(roleService.getById(user.getRoleId()));
// 第一个参数随便放,可以是user,在系统中任意位置可以获取改对象;
// 第二个参数必须是密码
// 第三个参数当前Realm的名称,因为可能存在多个Realm
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
stopPreviousSession(user.getId());
return info;
}
/**
* 超级管理员自动获取所有权限
*/
@Override
public boolean isPermitted(PrincipalCollection principals, String permission) {
User user = ((User) principals.getPrimaryPrincipal());
if (Role.ADMIN_FLAG_SUPER_ADMIN == user.getRole().getAdminFlag()) {
return true;
}
return isPermitted(principals, getPermissionResolver().resolvePermission(permission));
}
@Override
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = getAuthorizationInfo(principals);
Collection<Permission> perms = getPermissions(info);
if (CollectionUtils.isEmpty(perms)) {
return false;
}
for (Permission perm : perms) {
if (perm.implies(permission)) {
return true;
}
}
return false;
}
/**
* 踢掉上一个登录的同名用户
*
* @param id 主键
*/
private void stopPreviousSession(Integer id) {
Collection<Session> sessions = sessionDAO.getActiveSessions();
Session currSession = SecurityUtils.getSubject().getSession();
Serializable sId = currSession.getId();
for (Session session : sessions) {
SimplePrincipalCollection collection = (SimplePrincipalCollection) session
.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (collection == null) {
continue;
}
User u = (User) collection.getPrimaryPrincipal();
if (id.equals(u.getId())) {
if (sId.equals(session.getId())) {
continue;
}
session.stop();
break;
}
}
}
}
2. ShiroWildcardPermission 类
implies方法就是用来校验权限通过与否的.写的比较简单粗浅,考虑的情况很简单....
假若用户拥有 /user/page 路径的访问权限,那么 /user/page/** 下的所有路径都会判定通过,
而 /user 路径判定不通过,/role 判定也不通过
package com.web.shiro;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.WildcardPermission;
import java.util.List;
import java.util.Set;
/**
* @author
* @date 2019/07/25 15:10
*/
public class ShiroWildcardPermission extends WildcardPermission {
ShiroWildcardPermission(String wildcardString) {
super(wildcardString, DEFAULT_CASE_SENSITIVE);
}
@Override
public boolean implies(Permission p) {
if (!(p instanceof ShiroWildcardPermission)) {
return false;
}
List<Set<String>> targetParts = ((ShiroWildcardPermission) p).getParts();
String targetUrl = targetParts.get(0).iterator().next();
String url = getParts().get(0).iterator().next();
if (targetUrl.startsWith(url)) {
if (targetUrl.equals(url)) {
return true;
}
return targetUrl.startsWith(url.concat("/"));
}
return false;
}
}
3. ShiroPermissionResolver 类
很简单,就是生成 ShiroWildcardPermission 类对象
package com.web.shiro;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.PermissionResolver;
/**
* @author
* @date 2019/07/25 15:05
*/
public class ShiroPermissionResolver implements PermissionResolver {
@Override
public Permission resolvePermission(String permissionString) {
return new ShiroWildcardPermission(permissionString);
}
}
4.CheckLoginFilter
onAccessDenied 方法 当用户没有登录的处理策略,这里是直接返回json
ShiroUtil类放到最后面贴出
package com.web.shiro;
import com.web.base.RestResponse;
import com.web.constant.ErrorCode;
import com.web.utils.ShiroUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* 登录认证过滤器
*
* @author
*/
public class CheckLoginFilter extends FormAuthenticationFilter {
/**
* 表示访问拒绝时是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)
*
* @param request 对象
* @param servletResponse 对象
* @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器
* @throws Exception 异常
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse servletResponse) throws Exception {
HttpServletResponse response = (HttpServletResponse) servletResponse;
Object user = SecurityUtils.getSubject().getPrincipal();
if (null != user) {
return true;
}
Map<String, String> data = new HashMap<>(1);
data.put("toLoginUrl", "/login");
RestResponse<Map> rsp = new RestResponse<>(ErrorCode.SESSION_TIMEOUT_ERROR);
rsp.setMsg("你还没有登录或者会话过期!!!!!!!!!");
rsp.setData(data);
ShiroUtil.errorResponse(response, rsp);
return false;
}
}
5.CheckPermissionsFilter
第一个方法判定权限的是否通过,getPathWithinApplication(request) 取得当前请求的url路径
方法会进入到UserRealm的 doGetAuthorizationInfo 方法进行授权,之后进入isPermitted方法进行初步处理,最终会进入ShiroWildcardPermission的 implies 进行鉴权.
第二个方法表示判定失败时的处理措施
package com.web.shiro;
import com.web.base.RestResponse;
import com.web.constant.ErrorCode;
import com.web.utils.ShiroUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
/**
* 检验权限过滤器
*
* @author
* @date 2019/07/24 14:34
*/
public class CheckPermissionsFilter extends PermissionsAuthorizationFilter {
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return SecurityUtils.getSubject().isPermitted(getPathWithinApplication(request));
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse r) {
if (null == SecurityUtils.getSubject().getPrincipals()) {
RestResponse rest = new RestResponse(ErrorCode.SESSION_TIMEOUT_ERROR);
ShiroUtil.errorResponse((HttpServletResponse) r, rest);
return false;
}
RestResponse rest = new RestResponse(ErrorCode.PERMIT_INSUFFICIENT);
rest.setMsg("你没有访问权限,请联系管理员");
ShiroUtil.errorResponse((HttpServletResponse) r, rest);
return false;
}
}
6.ShiroLifecycleBeanPostProcessorConfig 类
里面只有一个bean
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author
* @date 2019/07/12 15:33
*/
@Configuration
public class ShiroLifecycleBeanPostProcessorConfig {
/**
* Shiro生命周期处理器
*
* @return LifecycleBeanPostProcessor
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
7.ShiroConfig 类
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
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.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Shiro 配置类
*
* @author
* @date 2019/07/09 10:13
*/
@Configuration
@AutoConfigureAfter(ShiroLifecycleBeanPostProcessorConfig.class)
public class ShiroConfig {
@Value("${shiro.session.global-session-timeout}")
String sessionTimeOut;
@Bean("userRealm")
public UserRealm userRealm() {
UserRealm realm = new UserRealm();
realm.setPermissionResolver(new ShiroPermissionResolver());
return realm;
}
/**
* 配置保存sessionId的cookie
* 注意:这里的cookie 不是上面的记住我 cookie 记住我需要一个cookie session管理 也需要自己的cookie
* 默认为: JSESSIONID 问题: 与SERVLET容器名冲突,重新定义为sid
*/
@Bean("sessionIdCookie")
public SimpleCookie sessionIdCookie() {
//这个参数是cookie的名称
SimpleCookie simpleCookie = new SimpleCookie("sid");
//setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
//setcookie()的第七个参数
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
//maxAge=-1表示浏览器关闭时失效此Cookie
simpleCookie.setMaxAge(1800);
return simpleCookie;
}
@Bean(name = "sessionDAO")
public MemorySessionDAO getMemorySessionDAO() {
return new MemorySessionDAO();
}
@Bean("sessionManager")
public DefaultWebSessionManager defaultWebSessionManager(
@Qualifier("sessionIdCookie") SimpleCookie simpleCookie,
@Qualifier("sessionDAO") MemorySessionDAO sessionDAO) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 全局会话超时时间,毫秒,目前是二十分钟
sessionManager.setGlobalSessionTimeout(Integer.parseInt(sessionTimeOut));
sessionManager.setSessionIdCookie(simpleCookie);
sessionManager.setSessionDAO(sessionDAO);
// 删除无效的session
sessionManager.setDeleteInvalidSessions(true);
// 是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
//设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
//设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
// 毫秒 十分钟
sessionManager.setSessionValidationInterval(600000);
//去掉URL中的JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
@Bean("securityManager")
public DefaultWebSecurityManager securityManager(UserRealm userRealm, DefaultWebSessionManager sessionManager) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setSessionManager(sessionManager);
manager.setRealm(userRealm);
return manager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* 可以在controller中的方法前加上注解
* 如 @RequiresPermissions("userInfo:add")
*
* @param securityManager 管理器
* @return AuthorizationAttributeSourceAdvisor
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
bean.setLoginUrl("/toLogin");
bean.setUnauthorizedUrl("/403");
// 定义过滤器
Map<String, Filter> filters = bean.getFilters();
filters.put("authc", new CheckLoginFilter());
filters.put("perms", new CheckPermissionsFilter());
bean.setFilters(filters);
// 配置访问权限
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/*.ico", "anon");
filterChainDefinitionMap.put("/**/*.js", "anon");
filterChainDefinitionMap.put("/**/*.css", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/interface/**", "anon");
filterChainDefinitionMap.put("/**", "authc,perms");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
}
其中 shiroFilter 就配置了访问策略
可以插入如下代码,
(因为可以我只是根据请求url判定,就没有写,当然即使加上了,在我这个代码里面也没有效果,
这和CheckPermissionsFilter 类的 isAccessAllowed,UserRealm类的 doGetAuthorizationInfo 授权方法,以及
ShiroWildcardPermission类的鉴权方法implies有关)
filterChainDefinitionMap.put("/user/**","perms[123]"); // /user 下的所有路径必须具备123权限才可以访问
filterChainDefinitionMap.put("/role/**","role[admin]"); // /role下的路径必须具备admin角色才可以访问
8.ShiroUtil 类
就是输出json提示信息
import com.fasterxml.jackson.databind.ObjectMapper;
import com.polaris.web.base.RestResponse;
import com.polaris.web.constant.ErrorCode;
import com.polaris.web.exception.PolarisException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author
* @date 2019/07/24 15:15
*/
public class ShiroUtil {
public static void errorResponse(HttpServletResponse response, RestResponse rest) {
response.setStatus(200);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try (PrintWriter writer = response.getWriter()) {
writer.write(new ObjectMapper().writeValueAsString(rest));
writer.flush();
} catch (IOException e) {
throw new PolarisException(ErrorCode.ServerError);
}
}
}
好,至此结束!
可以通过断点了解执行流程,这样可以方便理解
水平有限,错误之处敬请指出...谢谢!!!