springboot集成shiro遭遇自定义filter异常的解决

springboot集成shiro遭遇自定义filter异常

首先简述springboot使用maven集成shiro

1、用maven添加shiro

<!--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>

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<String, Filter> 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<String, String> 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 = "<script type='text/javascript'>var wp=window.parent; if(wp!=null){while(wp.parent&&wp.parent!==wp){wp=wp.parent;}wp.location.href='%1$s';}else{window.location.href='%1$s';}</script>";

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<OrganizationResources> 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加载到它的过滤器链中(待深究!)。

shiro自定义异常无效

一定要看一下自己重写AuthorizingRealm中doGetAuthenticationInfo方法时候抛出什么异常,这抛出得是AuthenticationToken;

if (userName == null) {

throw new AuthenticationToken("用户名不正确");

} else if (!userPwd.equals(password )) {

throw new AuthenticationToken("密码不正确");

}

而我在controller里面抛出得却是UnknownAccountException,这样能捕捉到想要得自定义异常才有鬼了!

// 执行认证登陆

try {

subject.login(token);

} catch (UnknownAccountException uae) {

map.put("msg","账号或密码不对");

map.put("code","21000");

return map;

}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

以上是 springboot集成shiro遭遇自定义filter异常的解决 的全部内容, 来源链接: utcz.com/p/250723.html

回到顶部