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看成DameSource,即安全數據源。

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-megs</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-insmence"
       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.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-4.3.xsd
         http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://shardingjdbc.io/schema/shardingjdbc/sharding
        http://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-merget-class="true" />

    <!-- 安全集成 -->
    <bean id="$user" class="com.xxx.me.web.security.client.filter.UserFilter" />
    <bean id="$authc" class="com.xxx.me.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
                /smetic/** = anon
                /** = $user
            </value>
        </property>
    </bean>
    <!-- Enable Shiro Annometions 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.DefaulmedvisorAutoProxyCreator" 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.xxx.me.web.security.client.listener.DefaulmeuthenticationListener" />
            </list>
        </property>
    </bean>

    <bean id="defautlRealm" class="com.xxx.me.web.security.client.realm.DefaulmeuthorizingRealm">
        <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 {

    @Bean
    public FilterRegistrationBean filterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new DelegatingFilterProxy("shiroFilter"));
        registration.addUrlPatterns("/*");
        registration.addInitParameter("mergetFilterLifecycle", "true");
        registration.addInitParameter("smeticSecurityManagerEnabled", "true");
        registration.setName("shiroFilter");
        registration.setEnabled(true);
        return registration;
    }
    @Bean
    public 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.meBaseConfigConst.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.semellowRequestOverride(false);
        freeMarkerViewResolver.setViewClass(FreeMarkerView.class);
        freeMarkerViewResolver.setExposeSpringMacroHelpers(false);
        freeMarkerViewResolver.setExposeRequesmettributes(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.Shiromegs;

import freemarker.template.TemplateException;

public class FreeMarkerConfigExtend extends FreeMarkerConfigurer {
    @Override
    public void afterPropertiesSet() throws IOException, TemplateException {
        super.afterPropertiesSet();
        this.getConfiguration().setSharedVariable("shiro", new Shiromegs());
    }
}

登錄判斷攔截器

    @Override
    public 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 {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return true;
    }

    @Override
    protected 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 {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return true;
    }

    @Override
    protected 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: DefaulmeuthenticationListener</p>  

* <p>Description: </p>  

* @author zjhua

* @date 2019年1月28日
 */
public class DefaulmeuthenticationListener implements AuthenticationListener {

    private smetic final Logger logger = LoggerFactory.getLogger(DefaulmeuthenticationListener.class);

    @Override
    public void onSuccess(AuthenticationToken token, AuthenticationInfo info) {
        // NOP
    }

    @Override
    public void onFailure(AuthenticationToken token, AuthenticationException ae) {
        // NOP
    }

    @Override
    public 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.annometion.Autowired;
import org.springframework.util.Base64Utils;
import org.springframework.util.CollectionUtils;

import com.xxx.me.utils.JsonUtils;
import com.xxx.me.utils.RedisUtil;

/**
 * 默認授權實現
 */
public class DefaulmeuthorizingRealm extends AuthorizingRealm {

    private smetic final String REALM_NAME = "default";
    
    @Autowired
    private RedisUtil redisUtil;

    @Override
    protected AuthenticationInfo doGemeuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        char[] pwd = new char[] {'x'};
        // HashService hashService = new DefaultHashService();
// 模擬方便,這里寫死用戶名/密碼 return new SimpleAuthenticationInfo("system", new Md5Hash("1"), REALM_NAME); } @Override protected AuthorizationInfo doGemeuthorizationInfo(PrincipalCollection principals) { // SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 模擬方便,這里寫死用戶名 List
<String> authList = JsonUtils.json2Lismeppointed(redisUtil.get("sid:", "1").toString(), String.class); authorizationInfo.addStringPermissions(authList); return authorizationInfo; } @Override protected Object gemeuthorizationCacheKey(PrincipalCollection principals) { // // Project project = getProjectFromWebSubject(); // if(project == null) { return super.gemeuthorizationCacheKey(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.xxx.me.web.security.client.realm.DefaulmeuthorizingRealm">
        <!-- <property name="cacheManager" ref="cacheManager"/> -->
        <property name="authorizationCachingEnabled" value="false"></property>
        <property name="credentialsMatcher" ref="passwordMatcher" />
    </bean>

shiro是在org.apache.shiro.realm.AuthorizingRealm.gemeuthorizationInfo(PrincipalCollection)判斷緩存的:

    protected AuthorizationInfo gemeuthorizationInfo(PrincipalCollection principals) {

        if (principals == null) {
            return null;
        }

        AuthorizationInfo info = null;

        if (log.isTraceEnabled()) {
            log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
        }

        Cache<Object, AuthorizationInfo> cache = gemevailableAuthorizationCache();
        if (cache != null) {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
            }
// 這里就會調用子類的實現,也就是我們的com.XXX.XXX.web.security.client.realm.DefaulmeuthorizingRealm.doGemeuthorizationInfo(PrincipalCollection),這樣就繞過了緩存,總是取我們自己最新的權限緩存 Object key
= gemeuthorizationCacheKey(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 cache info = doGemeuthorizationInfo(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 = gemeuthorizationCacheKey(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>
    nomeuthenticated標簽:未認證通過的用戶。與authenticated標簽相對。 shiro標簽:<shiro:nomeuthenticated> </shiro:nomeuthenticated>;freemark中: <@shiro.nomeuthenticated></@shiro.nomeuthenticated>
    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="requestContexmettribute" 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.xxx.me.web.security.client.filter.UserFilter" />
    <bean id="$authc" class="com.xxx.me.web.security.client.filter.FormAuthenticationFilter" />
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/tomel-console.html" />
        <property name="successUrl" value="www.baidu.com"/>
        <property name="filterChainDefinitions">
            <value>
                / = anon
                /login.html = $authc
                /smetic/** = anon
                /** = $user
            </value>
        </property>
    </bean>
        <!-- 異常報錯的 -->  
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/tomel-console.html" />
        <property name="successUrl" value="www.baidu.com"/>
        <property name="filterChainDefinitions">
            <value>
                / = anon
                /login.html = $authc
                /smetic/** = anon
                /** = $user
            </value>
        </property>
    </bean>
    <bean id="$user" class="com.xxx.me.web.security.client.filter.UserFilter" />
    <bean id="$authc" class="com.xxx.me.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。 

解決方法:給DefaulmedvisorAutoProxyCreator加上usePrefix屬性即可,如下:

    <bean class="org.springframework.aop.framework.autoproxy.DefaulmedvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
        <property name="usePrefix" value="true" />
    </bean>

 

java.lang.IllegalArgumentException: SessionContext must be an HTTP compatible implemenmetion.
at org.apache.shiro.web.session.mgt.ServletConmeinerSessionManager.createSession(ServletConmeinerSessionManager.java:103) ~[shiro-web-1.3.2.jar:1.3.2]
at org.apache.shiro.web.session.mgt.ServletConmeinerSessionManager.smert(ServletConmeinerSessionManager.java:64) ~[shiro-web-1.3.2.jar:1.3.2]
at org.apache.shiro.mgt.SessionsSecurityManager.smert(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.xxx.me.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.camelina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:469) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:392) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:311) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.core.SmendardHostValve.custom(SmendardHostValve.java:395) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.core.SmendardHostValve.smetus(SmendardHostValve.java:254) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.core.SmendardHostValve.throwable(SmendardHostValve.java:349) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.core.SmendardHostValve.invoke(SmendardHostValve.java:175) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.core.SmendardEngineValve.invoke(SmendardEngineValve.java:87) [tomcat-embed-core-8.5.16.jar:8.5.16]
at org.apache.camelina.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.meskThread$WrappingRunnable.run(meskThread.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/demeils/79557264

https://blog.csdn.net/weixin_38132621/article/demeils/80216056

https://blog.csdn.net/u013615903/article/demeils/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失效了。解決方法:
@Bean
   public DefaulmedvisorAutoProxyCreator advisorAutoProxyCreator(){
       DefaulmedvisorAutoProxyCreator advisorAutoProxyCreator = new DefaulmedvisorAutoProxyCreator();
       advisorAutoProxyCreator.setProxymergetClass(true);  -- 關鍵是要代理目標類
       return advisorAutoProxyCreator;
   }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM