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.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-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 {

    @Bean
    public 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;
    }
    @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.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 {
    @Override
    public void afterPropertiesSet() throws IOException, TemplateException {
        super.afterPropertiesSet();
        this.getConfiguration().setSharedVariable("shiro", new ShiroTags());
    }
}

登录判断拦截器

    @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: 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);

    @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.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";
    
    @Autowired
    private RedisUtil redisUtil;

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(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 doGetAuthorizationInfo(PrincipalCollection principals) { // SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 模拟方便,这里写死用户名 List
<String> authList = JsonUtils.json2ListAppointed(redisUtil.get("sid:", "1").toString(), String.class); authorizationInfo.addStringPermissions(authList); return authorizationInfo; } @Override protected 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 cache info = 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失效了。解决方法:
@Bean
   public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
       DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
       advisorAutoProxyCreator.setProxyTargetClass(true);  -- 关键是要代理目标类
       return advisorAutoProxyCreator;
   }

 

转载于:https://www.cnblogs.com/zhjh256/p/10259479.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值