springboot shiro和freemarker集成之权限控制完全参考手册(跳过认证,登录由三方验证,全网首发)...
本文主要考慮單點登錄場景,登錄由其他系統負責,業務子系統只使用shiro進行菜單和功能權限校驗,登錄信息通過token從redis取得,這樣登錄驗證和授權就相互解耦了。
用戶、角色、權限進行集中式管理。網上不少這樣的提問,但是沒有解決方案、抑或只是說明如何做,并沒有完整的現成解決方法。
Apache Shiro 是Java 的一個安全框架,和Spring Security并駕齊驅,能夠很好的和freemarker、thymeleaf無縫集成,同時能夠無縫的應用于restful方法,這一點很重要,能夠很方便的進行維護。
Shiro的架構
?
其中認證和授權都是必須的,而不是可選的,這一點很多文檔并沒有很明確的說明,準確的說不管是否用三方登錄驗證,使用shiro的話,shiro的整個骨架都得過一遍,一開始我也是認為認證是可以跳過的,為此浪費了幾個小時。
會話和緩存可以使用redis替換默認的實現。
掌握shiro必須理解下列關鍵概念,這一點可能是一開始不理解shiro機制的時候覺得難以找到套路的原因。
?
- Subject:主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有Subject 都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執行者;
- SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager 交互;且它管理著所有Subject;可以看出它是Shiro 的核心,它負責與后邊介紹的其他組件進行交互,如果學習過SpringMVC,你可以把它看成DispatcherServlet前端控制器;
- Realm:域,Shiro從從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源。
subject其實是mvc負責生成的,例如spring mvc,以認證為例:
其中.login是在spring mvc域,Subject在org.apache.shiro.subject.Subject.Builder.buildSubject()生成。
?
對于我們而言,最簡單的一個Shiro 應用:
1、應用代碼通過Subject來進行認證和授權,而Subject又委托給SecurityManager;
2、我們需要給Shiro 的SecurityManager 注入Realm,從而讓SecurityManager 能得到合法的用戶及其權限進行判斷。
現在開始講解完整的實現。
?maven依賴
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-aspectj</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version></dependency><dependency><groupId>net.mingsoft</groupId><artifactId>shiro-freemarker-tags</artifactId><version>1.0.0</version></dependency>?
spring配置文件,application-mvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:util="http://www.springframework.org/schema/util"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:p="http://www.springframework.org/schema/p"xmlns:sharding="http://shardingjdbc.io/schema/shardingjdbc/sharding"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-4.3.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.3.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.3.xsdhttp://shardingjdbc.io/schema/shardingjdbc/shardinghttp://shardingjdbc.io/schema/shardingjdbc/sharding/sharding.xsd"default-lazy-init="true"><context:property-placeholder location="classpath*:jrescloud.properties" ignore-unresolvable="true" order="1"/><aop:aspectj-autoproxy proxy-target-class="true" /><!-- 安全集成 --><bean id="$user" class="com.hundsun.ta.web.security.client.filter.UserFilter" /><bean id="$authc" class="com.hundsun.ta.web.security.client.filter.FormAuthenticationFilter" /><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager" /><property name="loginUrl" value="/console.html" /><property name="filterChainDefinitions"><value>/ = anon/login.html = $authc/static/** = anon/** = $user</value></property></bean><!-- Enable Shiro Annotations for Spring-configured beans. Only run after --><!-- the lifecycleBeanProcessor has run: --><bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"><property name="usePrefix" value="true" /></bean><bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"><property name="securityManager" ref="securityManager"/></bean><!-- Security Manager --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="authenticator" ref="authenticator" /><property name="realm" ref="defautlRealm" /></bean><bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"><property name="authenticationListeners"><list><bean class="com.hundsun.ta.web.security.client.listener.DefaultAuthenticationListener" /></list></property></bean><bean id="defautlRealm" class="com.hundsun.ta.web.security.client.realm.DefaultAuthorizingRealm"><property name="cacheManager" ref="cacheManager"/><property name="credentialsMatcher" ref="passwordMatcher" /></bean><bean id="passwordMatcher" class="org.apache.shiro.authc.credential.PasswordMatcher"><property name="passwordService" ref="passwordService"/></bean><bean id="passwordService" class="org.apache.shiro.authc.credential.DefaultPasswordService"><property name="hashService"><bean class="org.apache.shiro.crypto.hash.DefaultHashService"><property name="hashAlgorithmName" value="MD5"/></bean></property></bean><bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" /><bean id="simpleCredentialsMatcher" class="org.apache.shiro.authc.credential.SimpleCredentialsMatcher"/><bean id="allowAllCredentialsMatcher" class="org.apache.shiro.authc.credential.AllowAllCredentialsMatcher"/> </beans>spring bean配置
@Configuration @ImportResource(locations = { "classpath*:application-mvc.xml" }) public class BaseWebAppConfig {@Beanpublic FilterRegistrationBean filterRegistration() {FilterRegistrationBean registration = new FilterRegistrationBean();registration.setFilter(new DelegatingFilterProxy("shiroFilter"));registration.addUrlPatterns("/*");registration.addInitParameter("targetFilterLifecycle", "true");registration.addInitParameter("staticSecurityManagerEnabled", "true");registration.setName("shiroFilter");registration.setEnabled(true);return registration;}@Beanpublic FreeMarkerConfigurer freemarkerConfig() throws IOException, TemplateException {FreeMarkerConfigExtend configurer = new FreeMarkerConfigExtend();configurer.setTemplateLoaderPath("classpath:/templates");configurer.setDefaultEncoding("UTF-8");Map<String, Object> freemarkerVariables = new HashMap<>();freemarkerVariables.put("appServiceUrl", env.getProperty(BaseConfig.TaBaseConfigConst.APP_SERVICE_URL));configurer.setFreemarkerVariables(freemarkerVariables);return configurer;}@ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true)public FreeMarkerViewResolver getFreemarkViewResolver() {FreeMarkerViewResolver freeMarkerViewResolver = new FreeMarkerViewResolver();freeMarkerViewResolver.setCache(false);freeMarkerViewResolver.setSuffix(".html");freeMarkerViewResolver.setContentType("text/html; charset=UTF-8");freeMarkerViewResolver.setAllowRequestOverride(false);freeMarkerViewResolver.setViewClass(FreeMarkerView.class);freeMarkerViewResolver.setExposeSpringMacroHelpers(false);freeMarkerViewResolver.setExposeRequestAttributes(false);freeMarkerViewResolver.setExposeSessionAttributes(false);return freeMarkerViewResolver;} }
java bean
import java.io.IOException;import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;import com.jagregory.shiro.freemarker.ShiroTags;import freemarker.template.TemplateException;public class FreeMarkerConfigExtend extends FreeMarkerConfigurer {@Overridepublic void afterPropertiesSet() throws IOException, TemplateException {super.afterPropertiesSet();this.getConfiguration().setSharedVariable("shiro", new ShiroTags());} }登錄判斷攔截器
@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String path = request.getContextPath().length() > 1 ? request.getRequestURI().replace(request.getContextPath(), "") : request.getRequestURI();// 登錄認證,模擬方便這里寫死訪問頁面和用戶名/密碼if (path.equals("/console.html")) {SecurityUtils.setSecurityManager(SpringContextHolder.getBean(DefaultWebSecurityManager.class));//得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken("system","1"); //登錄密碼 subject.login(token);}}filter實現
import javax.servlet.ServletRequest; import javax.servlet.ServletResponse;import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject;/*** shiro單點登錄認證 * <p>Title: FormAuthenticationFilter</p> * <p>Description: </p> * @author zjhua * @date 2019年1月28日*/ public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {return true;}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {return true;}/*** 登錄成功后,將原來的session注銷,新增新的session* @param token* @param subject* @param request* @param response* @return* @throws Exception*/protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception{return true;} } import javax.servlet.ServletRequest; import javax.servlet.ServletResponse;/*** 用戶登錄校驗過濾器*/ public class UserFilter extends org.apache.shiro.web.filter.authc.UserFilter {@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {return true;}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {return true;} } import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationListener; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** 默認認證實現 * <p>Title: DefaultAuthenticationListener</p> * <p>Description: </p> * @author zjhua* @date 2019年1月28日*/ public class DefaultAuthenticationListener implements AuthenticationListener {private static final Logger logger = LoggerFactory.getLogger(DefaultAuthenticationListener.class);@Overridepublic void onSuccess(AuthenticationToken token, AuthenticationInfo info) {// NOP }@Overridepublic void onFailure(AuthenticationToken token, AuthenticationException ae) {// NOP }@Overridepublic void onLogout(PrincipalCollection principals) {// NOP }} import java.util.Arrays; import java.util.Collections; import java.util.List;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.SimpleAuthorizationInfo; import org.apache.shiro.crypto.hash.DefaultHashService; import org.apache.shiro.crypto.hash.Hash; import org.apache.shiro.crypto.hash.HashRequest; import org.apache.shiro.crypto.hash.HashService; import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Base64Utils; import org.springframework.util.CollectionUtils;import com.hundsun.ta.utils.JsonUtils; import com.hundsun.ta.utils.RedisUtil;/*** 默認授權實現*/ public class DefaultAuthorizingRealm extends AuthorizingRealm {private static final String REALM_NAME = "default";@Autowiredprivate RedisUtil redisUtil;@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {char[] pwd = new char[] {'x'};// HashService hashService = new DefaultHashService();// 模擬方便,這里寫死用戶名/密碼return new SimpleAuthenticationInfo("system", new Md5Hash("1"), REALM_NAME);}@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 模擬方便,這里寫死用戶名List<String> authList = JsonUtils.json2ListAppointed(redisUtil.get("sid:", "1").toString(), String.class);authorizationInfo.addStringPermissions(authList);return authorizationInfo;}@Overrideprotected Object getAuthorizationCacheKey(PrincipalCollection principals) {//// Project project = getProjectFromWebSubject();// if(project == null) {return super.getAuthorizationCacheKey(principals);// }// return principals.getPrimaryPrincipal().toString() + "#" + project.getId(); } }
上述就是完整的代碼了,這樣shiro就相當于實現了只有權限、沒有認證過程,因為我們可以基于token得到認證信息直接完成。
示例:
@RequiresPermissions("order:view")@RequestMapping("/demo/vue-page")public String vuePage(Model m) {return "/demo/vue-page" } <@shiro.hasPermission name="order:add"><el-button size="small" @click="showAddDialog">新增(彈框模式)</el-button></@shiro.hasPermission>
?上述配置完成之后,整個就打通了,但是還存在一個問題,就是在進行權限校驗的時候,shiro是把權限保存在org.apache.shiro.cache.MemoryConstrainedCacheManager中,它是JVM本地緩存,這會導致基礎系統修改之后,權限無法生效,因為shiro的默認機制是退出然后重新登錄才會去取。對此有兩種解決方法:一種是自己實現緩存(本來想集成shiro-redis,發現還是自己控制最合適),另外一種是禁用緩存。此處先說明第二種。將defautlRealm改為如下即可:
<bean id="defautlRealm" class="com.hundsun.ta.web.security.client.realm.DefaultAuthorizingRealm"><!-- <property name="cacheManager" ref="cacheManager"/> --><property name="authorizationCachingEnabled" value="false"></property><property name="credentialsMatcher" ref="passwordMatcher" /></bean>shiro是在org.apache.shiro.realm.AuthorizingRealm.getAuthorizationInfo(PrincipalCollection)判斷緩存的:
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {if (principals == null) {return null;}AuthorizationInfo info = null;if (log.isTraceEnabled()) {log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");}Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();if (cache != null) {if (log.isTraceEnabled()) {log.trace("Attempting to retrieve the AuthorizationInfo from cache.");}// 這里就會調用子類的實現,也就是我們的com.XXX.XXX.web.security.client.realm.DefaultAuthorizingRealm.doGetAuthorizationInfo(PrincipalCollection),這樣就繞過了緩存,總是取我們自己最新的權限緩存Object key = getAuthorizationCacheKey(principals);info = cache.get(key);if (log.isTraceEnabled()) {if (info == null) {log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");} else {log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");}}}if (info == null) {// Call template method if the info was not found in a cacheinfo = doGetAuthorizationInfo(principals);// If the info is not null and the cache has been created, then cache the authorization info.if (info != null && cache != null) {if (log.isTraceEnabled()) {log.trace("Caching authorization info for principals: [" + principals + "].");}Object key = getAuthorizationCacheKey(principals);cache.put(key, info);}}return info;}
這種方式還有一個缺陷就是會導致rpc較多,后面只要實現自己的CacheManager引用本地,然后監聽基礎應用的Logout事件去更新即可,后面再講。
Shiro包含的標簽? ?
?guest標簽:驗證當前用戶是否為“訪客”,即未認證(包含未記住)的用戶;shiro標簽:<shiro:guest></shiro:guest> ?;freemark中: <@shiro.guest> ?</@shiro.guest>?
? ??user標簽:認證通過或已記住的用戶 shiro標簽:<shiro:user>?</shiro:user> ?;freemark中:?<@shiro.user>?</@shiro.user>?
? ??authenticated標簽:已認證通過的用戶。不包含已記住的用戶,這是與user標簽的區別所在。 shiro標簽:<shiro:authenticated>?</shiro:authenticated>;freemark中:?<@shiro.authenticated></@shiro.authenticated>
? ??notAuthenticated標簽:未認證通過的用戶。與authenticated標簽相對。 shiro標簽:<shiro:notAuthenticated>?</shiro:notAuthenticated>;freemark中:?<@shiro.notAuthenticated></@shiro.notAuthenticated>
? ??principal標簽:輸出當前用戶信息,通常為登錄帳號信息 ?shiro標簽:Hello, ?<@shiro.principal property="name" /> ?;freemarker中:??Hello, ?<@shiro.principal property="name" />, how are you today? ?? ?
? ??hasRole標簽:驗證當前用戶是否屬于該角色 ,shiro標簽:?<shiro:hasRole name="administrator">??Administer the system?</shiro:hasRole> ;freemarker中:<@shiro.hasRole name=”admin”>Hello admin!</@shiro.hasRole>?
? ??hasAnyRoles標簽:驗證當前用戶是否屬于這些角色中的任何一個,角色之間逗號分隔 ,shiro標簽:?<shiro:hasAnyRoles name="admin,user,operator">??Administer the system?</shiro:hasAnyRoles> ;freemarker中:<@shiro.hasAnyRoles name="admin,user,operator">Hello admin!</@shiro.hasAnyRoles>
? ??hasPermission標簽:驗證當前用戶是否擁有該權限 ,shiro標簽:?<shiro:hasPermission?name="/order:*">??訂單?</shiro:hasPermission> ;freemarker中:<@shiro.hasPermission?name="/order:*">訂單/@shiro.hasPermission> (一般來說,主要使用這個)
? ??lacksRole標簽:驗證當前用戶不屬于該角色,與hasRole標簽想反,shiro標簽:?<shiro:hasRole name="admin">??Administer the system?</shiro:hasRole> ;freemarker中:<@shiro.hasRole name="admin">Hello admin!</@shiro.hasRole>?
? ??lacksPermission標簽:驗證當前用戶不擁有某種權限,與hasPermission標簽是相對的,shiro標簽:?<shiro:lacksPermission?name="/order:*">?trade?</shiro:lacksPermission> ;freemarker中:<@shiro.lacksPermission?name="/order:*">trade</@shiro.lacksPermission>?
其他
使用的環境為Spring MVC+FreeMarker,要在ftl頁面中使用contextPath,需要在viewResolver中做如下配置(紅色部分):
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="suffix" value=".ftl" />
<property name="exposeSpringMacroHelpers" value="true"/>
<property name="requestContextAttribute" value="rc"></property>
</bean>
這樣,在頁面中使用${rc.contextPath} 就可獲得contextPath。
org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method
沒有登錄,沒有認證信息的原因。?
Shiro權限配置錯誤There is no filter with name 'anno' to apply to chain
?org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shiroFilter': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: There is no filter with name 'anno' to apply to chain [/preLogin] in the pool of available Filters. Ensure a filter with that name/path has first been registered with the addFilter method(s).
有可能是順序的問題(至少筆者碰到的是這樣),先配置anno即可,如下:
<!-- 正確,沒有問題的 --><bean id="$user" class="com.hundsun.ta.web.security.client.filter.UserFilter" /><bean id="$authc" class="com.hundsun.ta.web.security.client.filter.FormAuthenticationFilter" /><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager" /><property name="loginUrl" value="/total-console.html" /><property name="successUrl" value="www.baidu.com"/><property name="filterChainDefinitions"><value>/ = anon/login.html = $authc/static/** = anon/** = $user</value></property></bean> <!-- 異常報錯的 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager" /><property name="loginUrl" value="/total-console.html" /><property name="successUrl" value="www.baidu.com"/><property name="filterChainDefinitions"><value>/ = anon/login.html = $authc/static/** = anon/** = $user</value></property></bean><bean id="$user" class="com.hundsun.ta.web.security.client.filter.UserFilter" /><bean id="$authc" class="com.hundsun.ta.web.security.client.filter.FormAuthenticationFilter" />shiro集成進來后,調用API直接404異常
@GetMapping("/user")@RequiresPermissions(value={"user:add","resource:delete"},logical = Logical.OR)public User getUserInfo(@RequestParam(value = "crsKey") String username){return userService.findByUsername(username);}如果把RequiresPermissions這行去掉,是可以正常訪問的,加上之后就是404。?
解決方法:給DefaultAdvisorAutoProxyCreator加上usePrefix屬性即可,如下:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"><property name="usePrefix" value="true" /></bean>?
java.lang.IllegalArgumentException: SessionContext must be an HTTP compatible implementation.
at org.apache.shiro.web.session.mgt.ServletContainerSessionManager.createSession(ServletContainerSessionManager.java:103) ~[shiro-web-1.3.2.jar:1.3.2]
at org.apache.shiro.web.session.mgt.ServletContainerSessionManager.start(ServletContainerSessionManager.java:64) ~[shiro-web-1.3.2.jar:1.3.2]
at org.apache.shiro.mgt.SessionsSecurityManager.start(SessionsSecurityManager.java:152) ~[shiro-core-1.3.2.jar:1.3.2]
at org.apache.shiro.subject.support.DelegatingSubject.getSession(DelegatingSubject.java:336) ~[shiro-core-1.3.2.jar:1.3.2]
at org.apache.shiro.subject.support.DelegatingSubject.getSession(DelegatingSubject.java:312) ~[shiro-core-1.3.2.jar:1.3.2]
at org.apache.shiro.mgt.DefaultSubjectDAO.mergePrincipals(DefaultSubjectDAO.java:204) ~[shiro-core-1.3.2.jar:1.3.2]
at org.apache.shiro.mgt.DefaultSubjectDAO.saveToSession(DefaultSubjectDAO.java:166) ~[shiro-core-1.3.2.jar:1.3.2]
at org.apache.shiro.mgt.DefaultSubjectDAO.save(DefaultSubjectDAO.java:147) ~[shiro-core-1.3.2.jar:1.3.2]
at org.apache.shiro.mgt.DefaultSecurityManager.save(DefaultSecurityManager.java:383) ~[shiro-core-1.3.2.jar:1.3.2]
at org.apache.shiro.mgt.DefaultSecurityManager.createSubject(DefaultSecurityManager.java:350) ~[shiro-core-1.3.2.jar:1.3.2]
at org.apache.shiro.mgt.DefaultSecurityManager.createSubject(DefaultSecurityManager.java:183) ~[shiro-core-1.3.2.jar:1.3.2]
at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:283) ~[shiro-core-1.3.2.jar:1.3.2]
at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:256) ~[shiro-core-1.3.2.jar:1.3.2]
at com.hundsun.ta.interceptor.SecurityInteceptor.preHandle(SecurityInteceptor.java:96) [classes/:?]
at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:133) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:962) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) [spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:469) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:392) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:311) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:395) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:254) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:349) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:175) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.16.jar:8.5.16]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_171]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_171]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.16.jar:8.5.16]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_171]
代碼如下:
SecurityUtils.setSecurityManager(SpringContextHolder.getBean(DefaultWebSecurityManager.class));//得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject = SecurityUtils.getSubject();if (!subject.isAuthenticated()) {logger.info(sessionBean.getId().toString() + "尚未登錄,開始自動登錄!");UsernamePasswordToken token = new UsernamePasswordToken(sessionBean.getId().toString(),sessionBean.getPassword() == null ? "1" : sessionBean.getPassword()); //登錄密碼try {subject.login(token);} catch (Exception e) {logger.error("自動登錄失敗!",e);}}有時候會報錯,有時候不報錯,這就比較坑爹了,一開始沒有找到規律。經過反復測試重現出來了,當沒有權限拋出異常后系統會跳轉到error頁面,于是又進入preHandler,再次去登錄,遂出現該問題,不是https://www.cnblogs.com/ningheshutong/p/6478080.html所述的問題,加上判斷如果是/error就不嘗試登錄,問題就解決,如下。
if (!path.equals("/error")) {SecurityUtils.setSecurityManager(SpringContextHolder.getBean("clientSecurityManager"));// 得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證)Subject subject = SecurityUtils.getSubject();if (!subject.isAuthenticated()) {logger.info(sessionBean.getId().toString() + "尚未登錄,開始自動登錄!");UsernamePasswordToken token = new UsernamePasswordToken(sessionBean.getId().toString(),sessionBean.getPassword() == null ? "1" : sessionBean.getPassword()); // 登錄密碼try {subject.login(token);} catch (Exception e) {response.sendRedirect(appWebHomeUrl + "/logout.html");logger.error("自動登錄失敗!", e);return false;}}}使用這種方式還有一個注意點,就是shiro的session超時時間設置,如下所示:
Shiro的Session接口有一個setTimeout()方法,登錄后,可以用如下方式取得session
SecurityUtils.getSubject().getSession().setTimeout(1800000);
設置的最大時間,正負都可以,為負數時表示永不超時。
SecurityUtils.getSubject().getSession().setTimeout(-1000l);
?默認為1800秒。
參考:
https://blog.csdn.net/qq_26321411/article/details/79557264
https://blog.csdn.net/weixin_38132621/article/details/80216056
https://blog.csdn.net/u013615903/article/details/78781166/
http://shiro.apache.org/
https://www.infoq.com/minibooks/apache-shiro-ee-7
http://shiro.apache.org/webapp-tutorial.html
http://shiro.apache.org/java-authorization-guide.html
http://shiro.apache.org/java-authentication-guide.html
其他異常
在Springboot環境中繼承Shiro時,使用注解@RequiresPermissions時無效,也就是似乎@RequestMapping失效了。解決方法: @Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true); -- 關鍵是要代理目標類return advisorAutoProxyCreator;}?
轉載于:https://www.cnblogs.com/zhjh256/p/10259479.html
總結
以上是生活随笔為你收集整理的springboot shiro和freemarker集成之权限控制完全参考手册(跳过认证,登录由三方验证,全网首发)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 编程快速上手 第十七章 操
- 下一篇: 程序员的数学--排列组合(2)