首先简述springboot使用maven集成shiro
1、用maven添加shiro
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-webartifactId>
<version>1.4.0version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.0version>
dependency>
2、配置shiro
import com.yuntu.intelligent.log.service.QueryPermissionService;
import com.yuntu.intelligent.log.service.shiro.authc.AccountSubjectFactory;
import com.yuntu.intelligent.log.service.shiro.filter.AuthenticatedFilter;
import com.yuntu.intelligent.log.service.shiro.filter.QueryLimitFiter;
import com.yuntu.intelligent.log.service.shiro.realm.AccountRealm;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.mgt.SessionManager;
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.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpSession;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* shiro权限管理的配置
*/
@Configuration
public class ShiroConfig {
@Bean
public AccountSubjectFactory accountSubjectFactory() {
return new AccountSubjectFactory();
}
/**
* 安全管理器
*/
@Bean
public DefaultWebSecurityManager securityManager(CookieRememberMeManager rememberMeManager, CacheManager cacheShiroManager, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(this.shiroAccountRealm());
securityManager.setCacheManager(cacheShiroManager);
securityManager.setRememberMeManager(rememberMeManager);
securityManager.setSessionManager(sessionManager);
securityManager.setSubjectFactory(this.accountSubjectFactory());
return securityManager;
}
/**
* session管理器(单机环境)
*/
@Bean
public DefaultWebSessionManager defaultWebSessionManager(CacheManager cacheShiroManager) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setCacheManager(cacheShiroManager);
sessionManager.setSessionValidationInterval(1800 * 1000);
sessionManager.setGlobalSessionTimeout(900 * 1000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
cookie.setName("shiroCookie");
cookie.setHttpOnly(true);
sessionManager.setSessionIdCookie(cookie);
return sessionManager;
}
/**
* 缓存管理器 使用Ehcache实现
*/
@Bean
public CacheManager getCacheShiroManager() {
return new MemoryConstrainedCacheManager();
}
/**
* 项目自定义的Realm
*/
@Bean
public AccountRealm shiroAccountRealm() {
return new AccountRealm();
}
/**
* rememberMe管理器, cipherKey生成见{@code Base64Test.java}
*/
@Bean
public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
CookieRememberMeManager manager = new CookieRememberMeManager();
manager.setCipherKey(Base64.decode("Z3VucwAAAAAAAAAAAAAAAA=="));
manager.setCookie(rememberMeCookie);
return manager;
}
/**
* 记住密码Cookie
*/
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天
return simpleCookie;
}
/**
* Shiro的过滤器链
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager,CollectionPropertiesConfig collectionPropertiesConfig,QueryPermissionService queryPermissionService) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
/**
* 默认的登陆访问url
*/
shiroFilter.setLoginUrl("/login");
/**
* 登陆成功后跳转的url
*/
shiroFilter.setSuccessUrl("/");
/**
* 没有权限跳转的url
*/
shiroFilter.setUnauthorizedUrl("/error/reject.html");
/**
* 覆盖默认的user拦截器(默认拦截器解决不了ajax请求 session超时的问题,若有更好的办法请及时反馈作者)
*/
HashMap myFilters = new HashMap<>();
myFilters.put("query", new QueryLimitFiter(queryPermissionService));
myFilters.put("authc", new AuthenticatedFilter());
shiroFilter.setFilters(myFilters);
/**
* 配置shiro拦截器链
*
* anon 不需要认证
* authc 需要认证
* user 验证通过或RememberMe登录的都可以
*
* 当应用开启了rememberMe时,用户下次访问时可以是一个user,但不会是authc,因为authc是需要重新认证的
*
* 顺序从上到下,优先级依次降低
*
*/
Map hashMap = new LinkedHashMap<>();
hashMap.put("/login", "anon");
hashMap.put("/", "authc");
hashMap.put("/user*", "authc");
hashMap.put("/user/**", "authc");
hashMap.put("/post/**", "authc");
hashMap.put("/admin", "authc,perms[admin]");
hashMap.put("/admin/**", "authc,perms[admin]");
for(String uri:collectionPropertiesConfig.getQueryLogUrls()){
hashMap.put(uri,"query");
}
shiroFilter.setFilterChainDefinitionMap(hashMap);
return shiroFilter;
}
/**
* 在方法中 注入 securityManager,进行代理控制
*/
@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean(DefaultWebSecurityManager securityManager) {
MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
bean.setArguments(new Object[]{securityManager});
return bean;
}
/**
* Shiro生命周期处理器:
* 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)
* 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 启用shrio授权注解拦截方式,AOP式方法级权限检查
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
3、实现自定义的Realm、filter、SubjectFactory等:
import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
public class AccountAuthenticationInfo extends SimpleAuthenticationInfo{
private static final long serialVersionUID = 3405356595200877071L;
private OrganizationUser profile;
public AccountAuthenticationInfo(){
}
public AccountAuthenticationInfo(Object principal, Object credentials, String realmName){
super(principal, credentials, realmName);
}
public OrganizationUser getProfile() {
return profile;
}
public void setProfile(OrganizationUser profile) {
this.profile = profile;
}
}
import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.subject.support.WebDelegatingSubject;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class AccountSubject extends WebDelegatingSubject{
private OrganizationUser profile;
public AccountSubject(PrincipalCollection principals, boolean authenticated, String host, Session session,
boolean sessionEnabled, ServletRequest request, ServletResponse response, SecurityManager securityManager, OrganizationUser profile) {
super(principals, authenticated, host, session, sessionEnabled, request, response, securityManager);
this.profile = profile;
}
public String getUsername(){
return getPrincipal().toString();
}
public OrganizationUser getProfile() {
return profile;
}
}
import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser;
import com.yuntu.intelligent.log.service.UserService;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.mgt.SubjectFactory;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.subject.WebSubjectContext;
import org.springframework.beans.factory.annotation.Autowired;
public class AccountSubjectFactory implements SubjectFactory {
@Autowired
private UserService userService;
@Override
public Subject createSubject(SubjectContext context) {
WebSubjectContext wsc = (WebSubjectContext) context;
AuthenticationInfo info = wsc.getAuthenticationInfo();
OrganizationUser profile = null;
AccountSubject subject = null;
if (info instanceof AccountAuthenticationInfo) {
profile = ((AccountAuthenticationInfo) info).getProfile();
subject = doCreate(wsc, profile);
subject.getSession(true).setAttribute("profile", profile);
}else{
Session session = wsc.getSession();
if(session != null){
profile = (OrganizationUser)session.getAttribute("profile");
}
subject = doCreate(wsc, profile);
boolean isRemembered = subject.isRemembered();
if (session == null) {
wsc.setSessionCreationEnabled(true);
subject.getSession(true);
}
if (isRemembered && profile == null) {
Object username = subject.getPrincipal();
profile = userService.getUserByName((String) username);
subject.getSession(true).setTimeout(30 * 60 * 1000);
subject.getSession(true).setAttribute("profile", profile);
}
}
return doCreate(wsc, profile);
}
private AccountSubject doCreate(WebSubjectContext wsc, OrganizationUser profile) {
return new AccountSubject(wsc.resolvePrincipals(), wsc.resolveAuthenticated(), wsc.resolveHost(),
wsc.resolveSession(), wsc.isSessionCreationEnabled(), wsc.resolveServletRequest(),
wsc.resolveServletResponse(), wsc.resolveSecurityManager(), profile);
}
}
import org.apache.shiro.authc.AuthenticationToken;
public class AccountToken implements AuthenticationToken {
private static final long serialVersionUID = 1L;
private long id;
private String username;
public AccountToken() {
}
public AccountToken(long id, final String username) {
this(id, username, username);
}
public AccountToken(long id, final String username, String nickname) {
this.id = id;
this.username = username;
}
@Override
public Object getPrincipal() {
return username;
}
@Override
public Object getCredentials() {
return null;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.servlet.OncePerRequestFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Formatter;
/**
* @version 1.0.0
*/
public class AuthenticatedFilter extends OncePerRequestFilter {
private Logger LOG = LoggerFactory.getLogger(AuthenticatedFilter.class);
private static final String JS = "";
private String loginUrl = "/login";
@Override
protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
LOG.info("开始权限验证");
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
chain.doFilter(request, response);
} else {
identifyGuest(subject, request, response, chain);
}
}
protected void identifyGuest(Subject subject, ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
redirectLogin(request, response);
}
protected void redirectLogin(ServletRequest request, ServletResponse response) throws IOException {
WebUtils.saveRequest(request);
String path = WebUtils.getContextPath((HttpServletRequest) request);
String url = loginUrl;
if (StringUtils.isNotBlank(path) && path.length() > 1) {
url = path + url;
}
if (isAjaxRequest((HttpServletRequest) request)) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print("您还没有登录!");
} else {
response.getWriter().write(new Formatter().format(JS, url).toString());
}
}
public String getLoginUrl() {
return loginUrl;
}
public void setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
}
/**
* 判断是否为Ajax请求 <功能详细描述>
*
* @param request
* @return 是true, 否false
* @see [类、类#方法、类#成员]
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
String header = request.getHeader("X-Requested-With");
if (header != null && "XMLHttpRequest".equals(header))
return true;
else
return false;
}
}
import com.yuntu.intelligent.log.service.QueryPermissionService;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.web.servlet.AbstractFilter;
import org.apache.shiro.web.servlet.ServletContextSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class QueryLimitFiter extends ServletContextSupport implements Filter {
private QueryPermissionService queryPermissionService;
private static final transient Logger log = LoggerFactory.getLogger(AbstractFilter.class);
protected FilterConfig filterConfig;
public QueryLimitFiter(QueryPermissionService queryPermissionService) {
this.queryPermissionService = queryPermissionService;
}
public FilterConfig getFilterConfig() {
return this.filterConfig;
}
public void setFilterConfig(FilterConfig filterConfig) {
this.filterConfig = filterConfig;
this.setServletContext(filterConfig.getServletContext());
}
protected String getInitParam(String paramName) {
FilterConfig config = this.getFilterConfig();
return config != null? org.apache.shiro.util.StringUtils.clean(config.getInitParameter(paramName)):null;
}
public final void init(FilterConfig filterConfig) throws ServletException {
this.setFilterConfig(filterConfig);
try {
this.onFilterConfigSet();
} catch (Exception var3) {
if(var3 instanceof ServletException) {
throw (ServletException)var3;
} else {
if(log.isErrorEnabled()) {
log.error("Unable to start Filter: [" + var3.getMessage() + "].", var3);
}
throw new ServletException(var3);
}
}
}
protected void onFilterConfigSet() throws Exception {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if(isAccessAllowed(servletRequest,servletResponse)){
filterChain.doFilter(servletRequest,servletResponse);
}else {
servletResponse.setContentType("application/json;charset=UTF-8");
servletResponse.getWriter().print("不允许查询!");
}
}
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse) {
HttpServletRequest request = (HttpServletRequest)servletRequest;
String hallCode = request.getParameter("guanhao");
String uri = request.getRequestURI();
System.out.println(uri);
// if (collectionPropertiesConfig.getQueryLogUrls().contains(uri)) {
try {
if (StringUtils.isEmpty(hallCode)) {
servletResponse.setContentType("application/json;charset=UTF-8");
servletResponse.getWriter().print("需要输入馆号!");
return false;
}
if (queryPermissionService.permit(hallCode)) {
return true;
} else {
servletResponse.setContentType("application/json;charset=UTF-8");
servletResponse.getWriter().print("你没有权限查询此馆!");
return false;
}
}catch (Exception e){
e.printStackTrace();
}
return false;
}
}
import com.yuntu.intelligent.log.model.sysmodel.OrganizationPrivilege;
import com.yuntu.intelligent.log.model.sysmodel.OrganizationResources;
import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser;
import com.yuntu.intelligent.log.service.RoleService;
import com.yuntu.intelligent.log.service.UserService;
import com.yuntu.intelligent.log.service.shiro.authc.AccountAuthenticationInfo;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
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 javax.annotation.Resource;
import java.util.List;
public class AccountRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService userRoleService;
public AccountRealm() {
super(new AllowAllCredentialsMatcher());
setAuthenticationTokenClass(UsernamePasswordToken.class);
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.fromRealm(getName()).iterator().next();
if (username != null) {
OrganizationUser user = userService.getUserByName(username);
if (user != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
OrganizationPrivilege role = userRoleService.getRole(user.getPrivilegeId());
List roleResources = userRoleService.getRoleResources(user.getPrivilegeId());
//赋予角色
info.addRole(role.getName());
//赋予权限
roleResources.forEach(resource -> info.addStringPermission(resource.getPermission()));
return info;
}
}
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
OrganizationUser profile = getAccount(userService, token);
if (profile.getStatus() == 0) {
throw new LockedAccountException(profile.getUserId());
}
AccountAuthenticationInfo info = new AccountAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());
info.setProfile(profile);
return info;
}
protected OrganizationUser getAccount(UserService userService, AuthenticationToken token) {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
return userService.login(upToken.getUsername(), String.valueOf(upToken.getPassword()));
}
}
4、重点记录filter配置中出现的问题。
如上代码,我有2个自定义的过滤器,AuthenticatedFilter可以抛开spring运行,而QueryLimitFiter想使用一些spring管理的bean,所以一开始QueryLimitFiter是使用@Component注释并且在ShiroConfig的@Bean方法中将其注入并配置进shiro的过滤器链。
在使用的时候,原定只是在个别uri触发的QueryLimitFiter,所有uri都会触发它,反而AuthenticatedFilter失效了。查找原因,找到spring的filter链如图,这是借用别人的图,我的图是将accessTokenFilter替换为queryLimitFiter:
总之就是自定义的过滤器QueryLimitFiter居然在shiroFilter之外而且运行在shiroFilter之前了。。。
5、解决方案,如上代码,将QueryLimitFiter不交给spring托管,使用new的方式添加到shiro的过滤器链中就没有问题了。出现原因可能是spring会自动将我们自定义的filter加载到它的过滤器链中(待深究!)。