Shiro学习笔记

简介

Shiro是强大、精简的Java安全框架。

在认证、授权、加密和会话管理等方面提供直观而又全面的解决方案。

wKiom1N7bBOTRPzSAADfpjXEj1k868.jpg

 

解释一下图中词汇:

  • Authentication:认证,简单理解为登录,证明自己是谁。
  • Authorization:授权,即谁是否有进行某操作的权限。
  • Session Management:用户会话管理,它是环境无关的,在任何应用都可以。
  • Cryptography:使用加密算法对数据进行加密,方法非常简便。
  • Web Support:对web应用提供非常简便的API。
  • Caching:缓存保证安全操作快速有效。
  • Concurrency:Shiro支持对多线程应用。
  • Testing:即使做了安全设置也不妨碍进行单元或者集成测试。
  • “Run As”:权限伪装。
  • Remember Me:登录后记住权限。

filterChainDefinitions定义的解决方案

 在Spring Context中定义shiroFilter(org.apache.shiro.spring.web.ShiroFilterFactoryBean)时需要为其filterChainDefinitions property赋值,譬如:

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login/in"/>
        <property name="unauthorizedUrl" value="/templates/error/unauthorized.html"/>
        <property name="filterChainDefinitions">
            <value>
                /login/out = logout
                /login/in = authc
                /supplier/** = authc, roles[supplier]
                /dealer/** = authc, roles[dealer]
      </value>
        </property>
    </bean>

有时会将每一个URL作为一个授权单位进行控制,但是URL的数量让人头疼,所以我要把这些URL放在数据库进行管理,可以不重启调整授权控制。我要把他们统统从数据库Query出来放到filterChainDefinitions里。

@Service("appChainDefinitionSection")
public class AppChainDefinitionSection implements FactoryBean<Ini.Section> {

    @Autowired
    private AppAuthcService appAuthcService ;

    @Override
    public Section getObject() throws Exception {
        //通过服务从数据库拉取APP应用的所有URL权限配置信息
        List<FilterChainResource> resourceVOList = appAuthcService.getAppFilterChainResource();
        Ini ini = new Ini();
        Ini.Section section = ini.addSection(Ini.DEFAULT_SECTION_NAME);
        for (FilterChainResource resourceVO : resourceVOList) {
            if (!StringUtils.isEmpty(resourceVO.getUrl())
                    && !StringUtils.isEmpty(resourceVO.getPermission())) {
                section.put(resourceVO.getUrl(), resourceVO.getPermission());
            }
        }
        return section;
    }


    @Override
    public Class<?> getObjectType() {
        return Ini.Section.class;
    }


    @Override
    public boolean isSingleton() {
        return true;
    }

}

public class FilterChainResource implements Serializable {

    private static final long serialVersionUID = 1L;

    //(primary = true, desc = "主键", autoAllocateId = true)
    private long id;

    //(desc = "访问路径定义", type = "VARCHAR(64)")
    private String url;

    //(desc = "访问权限定义", type = "VARCHAR(64)")
    private String permission;

    //(desc = "权限系统类别,0:CMS, 1:PIS, 2:MAINSITE, 3:MOBILE")
    private AuthzCategory category;

    @AnnonOfField(desc = "排序顺序")
    private int orderBy;

    .........................................
  
}

shiroSecurityFilter的介绍

拦截需要被拦截的URL,并进行create token并执行登录。

@ImportResource(value = "classpath:config/shiro-config.xml")
public class BaseSecurityConfig {

    @Autowired
    protected Environment env;

    @Autowired
    protected ApplicationContext context;

    @Autowired
    private FactoryBean<Ini.Section> appChainDefinitionSection;

    @Bean
    //注册shiro的过滤器
    protected FilterRegistrationBean shiroSecurityFilterRegistration() throws Exception {
        try {
            FilterRegistrationBean registrationBean = new FilterRegistrationBean();
            registrationBean.setFilter((Filter) shiroSecurityFilter().getObject());
            registrationBean.setOrder(1);
            return registrationBean;
        } catch (Exception e) {
            LOGGER.error("Failed to register shiro security filter!");
            throw e;
        }
    }

    private ShiroFilterFactoryBean shiroSecurityFilter() {
        ShiroFilterFactoryBean shiroSecurityFilter = new ShiroFilterFactoryBean();
        shiroSecurityFilter.setLoginUrl(env.getProperty(PROPERTY_WEB_LOGIN_URL));
        shiroSecurityFilter.setUnauthorizedUrl(env.getProperty(PROPERTY_WEB_UNAUTHORIZED_URL));
        shiroSecurityFilter.setSecurityManager(context.getBean(BEAN_NAME_SECURITY_MANAGER,DefaultWebSecurityManager.class));
        shiroSecurityFilter.setFilters(buildFilters());
        try {
            shiroSecurityFilter.setFilterChainDefinitionMap(appChainDefinitionSection.getObject());
        } catch (Throwable e) {
            LOGGER.error("Failed to load application chain definition!");
        }
        return shiroSecurityFilter;
    }

    private Map<String, Filter> buildFilters() {
        Map<String, Filter> filterMap = new LinkedHashMap<String, Filter>();
        filterMap.put("authProduct", authProductFilter());
        filterMap.put("authOrder", authOrderFilter());
        return filterMap;
    }

    private Filter authProductFilter() {
        ProductSecurityFilter filter = new ProductSecurityFilter();
        ....................................................
        ....................................................
        ....................................................
        return filter;
    }

    private Filter authOrderFilter() {
        OrderSecurityFilter filter = new OrderSecurityFilter();
        ....................................................
        ....................................................
        ....................................................
        return filter;
    }
    
}

 

public abstract class BaseSecurityFilter extends AccessControlFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        // 返回false由 #onAccessDenied()来继续处理
        return isLoginRequest(request, response) || isLoggedIn(request, response);
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        boolean loggedIn = false;
        if (isLoginAttempt(request, response)) {
            loggedIn = executeLogin(request, response);
        }
        //登录失败了就重定向到登录页
        if (!loggedIn) {
            redirectToLogin(request, response);
        }
        return loggedIn;
    }

    protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response);

    protected abstract boolean isLoggedIn(ServletRequest request, ServletResponse response);

    protected abstract boolean isLoginAttempt(ServletRequest request, ServletResponse response);


    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        // 1.获取用户访问凭证
        AuthenticationToken token = createToken(request, response);
        if (token == null)
            return false;

        // 2.提交凭证
        try {
            Subject subject = getSubject(request, response);
            subject.login(token);
            return true;
        } catch (AuthenticationException ae) {
            // 当捕获的异常类型为未授权账户或已冻结时,需跳转到对应的错误页面,因此继续向上抛出。
            if (ae instanceof UnknownAccountException || ae instanceof LockedAccountException) {
                throw ae;
            }
            return false;
        } 
    }
 }

  

这些仅仅是定义了哪些URL需要认证哪些URL不要认证。我们需要在用户访问这些URL的时候去验证一下用户(角色控制)是否具备当前URL权限。

 

 AuthorizingRealm的使用

当filter执行subject.login(token);时会调用AuthorizingRealm的doGetAuthenticationInfo执行真正的数据库数据和token数据的配对。

public class CustomAuthorizingRealm extends AuthorizingRealm {

    @Autowired
    private AuthUserService authUserService;

     // 认证回调函数,登录时调用.
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        AuthenticationToken token =  authcToken;
        String userName = token.getUserName();
        // 1.用户名为空,未通过URS认证
        if (!StringUtils.hasText(userName))
            throw new UnknownAccountException("userName is null");

        // 2.根据用户名获取用户信息,取不到则创建
        SimpleUser simpleUser = authUserService.findUser(userName,token.getPassword());
        if (simpleUser == null)
            throw new UnknownAccountException(userName + " Not Found");
        if (simpleUser.isLocked())
            throw new LockedAccountException("The account for username " + userName
                    + " is locked. Please contact your administrator to unlock it.");

        // 3.记录用户当前访问的详细信息
        AccessUser neAccessUser = new AccessUser();
        neAccessUser.setVistorId(simpleUser.getId());
        neAccessUser.setVistorName(simpleUser.getUserName());
        neAccessUser.setNickName(simpleUser.getNickName());
        neAccessUser.setLock(simpleUser.isLocked());
        
        return new SimpleAuthenticationInfo(neAccessUser, token.getCredentials(), getName());
    }
    
    // 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 1.根据用户名获取用户角色和权限信息
        AccessUser neAccessUser = principals.getPrimaryPrincipal();
        String userName = neAccessUser.getVistorName();
        List<SimpleRole> list = authUserService.getRoles(userName);
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        if (list != null && !list.isEmpty()) {
            for (DistributionSimpleRole role : list) {
                // 基于Role的权限信息teacher,student,boss
                info.addRole(role.getName());
                // 基于Permission的权限信息
                info.addStringPermissions(role.getPermissionList());

 


            }
        }
        return info;

    }
    
}

 

Session的管理

 

public class CustomSessionDAO extends AbstractSessionDAO {

    //自行选择一个分布式存储方式
    @Autowired
    RedisTemplate<Serializable, Session> template;

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        template.opsForValue().set(sessionId, session, 30, TimeUnit.MINUTES);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        Session session = null;
        if (sessionId != null) {
            session = template.opsForValue().get(sessionId);
            if (session != null) {
                template.expire(sessionId, 30, TimeUnit.MINUTES);
            }
        }
        return session;
    }
    @Override
    public void update(Session session) throws UnknownSessionException {
        if (session == null) {
            return;
        }
        template.opsForValue().set(session.getId(), session, 30, TimeUnit.MINUTES);
    }

    @Override
    public void delete(Session session) {
        System.out.println("删除session:" + session.getHost());
    }

    @Override
    public Collection<Session> getActiveSessions() {
        return null;
    }
}

 

与spring的集成

<?xml version="1.0" encoding="UTF-8"?>
<beans>

  <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
  <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />   
    <!--自定义session管理-->
    <bean id="customeSessionDAO" class="com...CustomSessionDAO"></bean>
    <bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!--session会话默认闲置半小时失效-->
        <property name="sessionDAO" ref="customeSessionDAO"/>
    </bean>
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customAuthorizingRealm"/>
        <property name="sessionManager" ref="defaultWebSessionManager"/>
    </bean>
  <!-- Spring Shiro AOP -->
   <aop:config proxy-target-class="true" />
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>    
  
  <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
     <property name="exceptionMappings">
        <props>
           <prop key="org.apache.shiro.authz.UnauthenticatedException">error/403</prop>
           <prop key="org.apache.shiro.authz.UnauthorizedException">error/403</prop>
           <prop key="org.apache.shiro.authc.LockedAccountException">error/locked</prop>
           <!-- <prop key="java.lang.Throwable">error/500</prop> -->
        </props>
     </property>
  </bean>
</beans>

***.properties
web.loginUrl=/login
web.unauthorizedUrl=/health
filter.chain.definitions=/s/** = anon\n/health/** = anon\n/logout = anon\n/login/** = anon\n/ext/** = anon\n/dealer/** = authMainsiteDealer\n/supplier/** = authMainsiteSupplier

 

controller与permission的配合

  • @RequiresAuthentication:访问或者调用被注解的类或者方法时通过认证。
  • @RequiresGuest:需要从未通过认证且没有被记住(Remember me)。
  • @RequiresPremissions:需要特定的权限。
  • @RequiresRoles:需要特定的角色。
  • @RequiresUser:需要已通过认证

 

posted @ 2016-07-21 17:10  wade&luffy  阅读(608)  评论(0)    收藏  举报