SpringBoot集成篇(一)无状态shiro

springboot是现如今很流行的微服务框架

鉴权方面内置了spring自家的spring security ,比较方便,这里阐述用springboot集成另一大身份验证和授权框架 shiro。


网上也有很多boot集成shiro的实例 但是都不太完整,且不是stateless无状态的,不适用于现在这种前后端分离格局。


这里特此记录下辛酸的集成过程,让大家少走一点弯路。

shiro的集成方式为无状态(禁用session),通过每次请求带上token进行鉴权。


为了演示 token禁用简单的uuid32生成。


jdk版本1.7 springboot版本1.5.3

springboot环境搭建这里不在说明,直入正题。


依赖

1.引入shiro的maven依赖,这里用最新的1.3.2版本


		
		    org.apache.shiro
		    shiro-core
		    1.3.2
		
		
		    org.apache.shiro
		    shiro-web
		    1.3.2
		
		
		    org.apache.shiro
		    shiro-spring
		    1.3.2
		


代码实现

1.ShiroConfig配置

package com.lhy.config;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import net.sf.ehcache.CacheManager;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.lhy.auth.service.MyAuthService;
import com.lhy.common.shiro.filter.StatelessAuthcFilter;
import com.lhy.common.shiro.realm.StatelessRealm;
import com.lhy.common.shiro.service.PrincipalService;
import com.lhy.common.shiro.subject.StatelessDefaultSubjectFactory;
import com.lhy.common.shiro.token.helper.EhCacheUserTokenHelper;
import com.lhy.common.shiro.token.manager.TokenManager;
import com.lhy.common.shiro.token.manager.impl.DefaultTokenManagerImpl;

/**
 * shiro配置
 * @author luanhy
 *
 */
@Configuration
public class ShiroConfig {
     
     private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
     
     /**
      * token管理类
      * @param cacheManager
      * @param bootProperties
      * @return
      */
     @Bean
     public TokenManager tokenManager(CacheManager cacheManager,BootProperties bootProperties){
          logger.info("ShiroConfig.getTokenManager()");
          //默认的token管理实现类 32位uuid
          DefaultTokenManagerImpl tokenManager = new DefaultTokenManagerImpl();
          //token失效时间
          tokenManager.setExpirateTime(bootProperties.getExpirateTime());
         
          //用户token委托给ehcache管理
          EhCacheUserTokenHelper ehCacheUserTokenHelper = new EhCacheUserTokenHelper();
          ehCacheUserTokenHelper.setCacheManager(cacheManager);
          tokenManager.setUserTokenOperHelper(ehCacheUserTokenHelper);
	    // 安全的jwttoken方式 不用担心token被拦截          
//          RaFilterJwtTokenManagerImpl tokenManager1 = new RaFilterJwtTokenManagerImpl();
//          JwtUtil jwtUtil = new JwtUtil();
//          jwtUtil.setProfiles(bootProperties.getKey());
//          tokenManager1.setJwtUtil(jwtUtil);
//          tokenManager1.setExpirateTime(bootProperties.getExpirateTime());
//          EhCacheLoginFlagHelper ehCacheLoginFlagHelper = new EhCacheLoginFlagHelper();
//          ehCacheLoginFlagHelper.setCacheManager(cacheManager);
//          tokenManager1.setLoginFlagOperHelper(ehCacheLoginFlagHelper);
//          tokenManager1.setUserTokenOperHelper(ehCacheUserTokenHelper);

          return tokenManager;
     }
     
     /**
      * 无状态域
      * @param tokenManager
      * @param principalService 登陆账号服务需要实现PrincipalService接口
      * @param authorizationService 授权服务 需要实现authorizationService接口
      * @return
      */
     @Bean
     public StatelessRealm statelessRealm(TokenManager tokenManager,@Qualifier("userService") PrincipalService principalService,MyAuthService authorizationService){
          logger.info("ShiroConfig.getStatelessRealm()");
          StatelessRealm realm = new StatelessRealm();
          realm.setTokenManager(tokenManager);
          realm.setPrincipalService(principalService);
          realm.setAuthorizationService(authorizationService);
          return realm;
     }
     
     /**
      * 会话管理类 禁用session
      * @return
      */
     @Bean
     public DefaultSessionManager defaultSessionManager(){
          logger.info("ShiroConfig.getDefaultSessionManager()");
          DefaultSessionManager manager = new DefaultSessionManager();
          manager.setSessionValidationSchedulerEnabled(false);
          return manager;
     }
     
     /**
      * 安全管理类
      * @param statelessRealm
      * @return
      */
     @Bean
     public DefaultWebSecurityManager defaultWebSecurityManager(StatelessRealm statelessRealm){
          logger.info("ShiroConfig.getDefaultWebSecurityManager()");
          DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
          
          //禁用sessionStorage
          DefaultSubjectDAO de = (DefaultSubjectDAO)manager.getSubjectDAO();
          DefaultSessionStorageEvaluator defaultSessionStorageEvaluator =(DefaultSessionStorageEvaluator)de.getSessionStorageEvaluator();
          defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
          
          manager.setRealm(statelessRealm);
          
          //无状态主题工程,禁止创建session
          StatelessDefaultSubjectFactory statelessDefaultSubjectFactory = new StatelessDefaultSubjectFactory();
          manager.setSubjectFactory(statelessDefaultSubjectFactory);
          
          manager.setSessionManager(defaultSessionManager());
          //设置了SecurityManager采用使用SecurityUtils的静态方法 获取用户等
          SecurityUtils.setSecurityManager(manager);
          return manager;
     }
     
     /**
      * Shiro生命周期处理
      * @return
      */
     @Bean
     public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
          logger.info("ShiroConfig.getLifecycleBeanPostProcessor()");
          return new LifecycleBeanPostProcessor();
     }
     
     
     /**
      * 身份验证过滤器
      * @param manager
      * @param tokenManager
      * @return
      */
     @Bean
     public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager manager,TokenManager tokenManager){
          logger.info("ShiroConfig.getShiroFilterFactoryBean()");
          ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
          bean.setSecurityManager(manager);
          Map filters = new HashMap();
          //无需增加 shiro默认会添加该filter
          //filters.put("anon", anonymousFilter()); 
          
          //无状态授权过滤器 
         //特别注意!自定义的StatelessAuthcFilter 
	 //不能声明为bean 否则shiro无法管理该filter生命周期,该过滤器会执行其他过滤器拦截过的路径
	 //这种情况通过普通spring项目集成shiro不会出现,boot集成会出现,搞了好久才明白,巨坑
          StatelessAuthcFilter statelessAuthcFilter = statelessAuthcFilter(tokenManager);
          filters.put("statelessAuthc", statelessAuthcFilter);
          bean.setFilters(filters);
          //注意是LinkedHashMap 保证有序
          Map filterChainDefinitionMap = new LinkedHashMap();
          
          //1, 相同url规则,后面定义的会覆盖前面定义的(执行的时候只执行最后一个)。
          //2, 两个url规则都可以匹配同一个url,只执行第一个
          filterChainDefinitionMap.put("/html/**", "anon");
          filterChainDefinitionMap.put("/resource/**", "anon");
          filterChainDefinitionMap.put("/login", "anon");
          filterChainDefinitionMap.put("/login/**", "anon");
          filterChainDefinitionMap.put("/favicon.ico", "anon");
          filterChainDefinitionMap.put("/**", "statelessAuthc");
          
          bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
          
          //字符串方式创建过滤链 \n换行 
//        String s = "/resource/**=anon\n/html/**=anon\n/login/**=anon\n/login=anon\n/**=statelessAuthc";
//        bean.setFilterChainDefinitions(s);
          return bean;
     }
     
   /**
    *   
    * @Function: ShiroConfig::anonymousFilter
    * @Description: 该过滤器无需增加 shiro默认会添加该filter
    * @return
    * @version: v1.0.0
    * @author: hyluan
    * @date: 2017年5月8日 下午5:39:10 
    *
    * Modification History:
    * Date         Author          Version            Description
    *-------------------------------------------------------------
    */
   public AnonymousFilter anonymousFilter(){
        logger.info("ShiroConfig.anonymousFilter()");
        return new AnonymousFilter();
   }
   
   /**
    * 
    * @Function: ShiroConfig::statelessAuthcFilter
    * @Description:  无状态授权过滤器 注意不能声明为bean 否则shiro无法管理该filter生命周期,
* 该过滤器会执行其他过滤器拦截过的路径 * @param tokenManager * @return * @version: v1.0.0 * @author: hyluan * @date: 2017年5月8日 下午5:38:55 * * Modification History: * Date Author Version Description *------------------------------------------------------------- */ public StatelessAuthcFilter statelessAuthcFilter(TokenManager tokenManager){ logger.info("ShiroConfig.statelessAuthcFilter()"); StatelessAuthcFilter statelessAuthcFilter = new StatelessAuthcFilter(); statelessAuthcFilter.setTokenManager(tokenManager); return statelessAuthcFilter; } }



2.BootProperties读取applicaton.properties配置文件

package com.lhy.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "shiro.token") 
public class BootProperties {
	
	private String key;
	 
	private long expirateTime;

	public String getKey() {
		return key;
	}

	public void setKey(String key) {
		this.key = key;
	}

	public long getExpirateTime() {
		return expirateTime;
	}

	public void setExpirateTime(long expirateTime) {
		this.expirateTime = expirateTime;
	}
	 
	 

}

application.properties

shiro.token.key=helloworld
shiro.token.expirateTime=900


3.StatelessAuthcFilter 自定义权限过滤器

package com.lhy.common.shiro.filter;

import java.io.IOException;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import com.lhy.common.shiro.token.StatelessToken;
import com.lhy.common.shiro.token.manager.TokenManager;
import com.lhy.common.shiro.util.RequestUtil;

/**
 * 无状态授权过滤器 
 * @author luanhy
 *
 */
public class StatelessAuthcFilter extends AccessControlFilter {
	
	
	private final Logger logger = Logger.getLogger(StatelessAuthcFilter.class);
	
	@Autowired
	private TokenManager tokenManager;
	
	public TokenManager getTokenManager() {
		return tokenManager;
	}

	public void setTokenManager(TokenManager tokenManager) {
		this.tokenManager = tokenManager;
	}

	@Override
	protected boolean isAccessAllowed(ServletRequest request,
			ServletResponse response, Object mappedValue) throws Exception {
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		logger.info("拦截到的url:" + httpRequest.getRequestURL().toString());
		// 前段token授权信息放在请求头中传入
		String authorization = RequestUtil.newInstance().getRequestHeader(
				(HttpServletRequest) request, "authorization");
		if (StringUtils.isEmpty(authorization)) {
			onLoginFail(response, "请求头不包含认证信息authorization");
			return false;
		}
		// 获取无状态Token
		StatelessToken accessToken = tokenManager.getToken(authorization);
		try {
			// 委托给Realm进行登录
			getSubject(request, response).login(accessToken);
		} catch (Exception e) {
			logger.error("auth error:" + e.getMessage(), e);
			onLoginFail(response, "auth error:" + e.getMessage()); // 6、登录失败
			return false;
		}
		// 通过isPermitted 才能调用doGetAuthorizationInfo方法获取权限信息
		getSubject(request, response).isPermitted(httpRequest.getRequestURI());
		return true;
	}

	@Override
	protected boolean onAccessDenied(ServletRequest request,
			ServletResponse response) throws Exception {
		return false;
	}
	
	 //登录失败时默认返回401状态码  
	  private void onLoginFail(ServletResponse response,String errorMsg) throws IOException {  
	    HttpServletResponse httpResponse = (HttpServletResponse) response;  
	    httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);  
	    httpResponse.setContentType("text/html");
	    httpResponse.setCharacterEncoding("utf-8");
	    httpResponse.getWriter().write(errorMsg);  
	    httpResponse.getWriter().close();
	  }  

}

3.StatelessRealm无状态域

package com.lhy.common.shiro.realm;

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.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import com.lhy.common.shiro.service.AuthorizationService;
import com.lhy.common.shiro.service.PrincipalService;
import com.lhy.common.shiro.token.StatelessToken;
import com.lhy.common.shiro.token.manager.TokenManager;

public class StatelessRealm extends AuthorizingRealm {
	
	private TokenManager tokenManager;
	
	@SuppressWarnings("rawtypes")
	private PrincipalService principalService;
	
	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof StatelessToken;
	}
	
	private AuthorizationService authorizationService;

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		 //根据用户名查找角色,请根据需求实现  
		String userCode = (String) principals.getPrimaryPrincipal(); 
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		List selectRoles = authorizationService.selectRoles(userCode);
		authorizationInfo.addRoles(selectRoles);
		return authorizationInfo;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		StatelessToken statelessToken = (StatelessToken)token;
		
		String userCode = (String)statelessToken.getPrincipal();
		
		checkUserExists(userCode);
		
		String credentials = (String)statelessToken.getCredentials();
		boolean checkToken = tokenManager.checkToken(statelessToken);
		if (checkToken) {
			return new SimpleAuthenticationInfo(userCode, credentials, super.getName());
		}else{
			throw new AuthenticationException("token认证失败");
		}
	}
	
	private void checkUserExists(String userCode) throws AuthenticationException {
		Object principal = principalService.select(userCode);
		if(principal == null){
			throw new UnknownAccountException("userCode "+userCode+" wasn't in the system");
		}
	}
	
	public TokenManager getTokenManager() {
		return tokenManager;
	}

	public void setTokenManager(TokenManager tokenManager) {
		this.tokenManager = tokenManager;
	}
	
	@SuppressWarnings("rawtypes")
	public PrincipalService getPrincipalService() {
		return principalService;
	}

	@SuppressWarnings("rawtypes")
	public void setPrincipalService(PrincipalService principalService) {
		this.principalService = principalService;
	}

	public AuthorizationService getAuthorizationService() {
		return authorizationService;
	}

	public void setAuthorizationService(AuthorizationService authorizationService) {
		this.authorizationService = authorizationService;
	}

}




3.PrincipalService用户服务

package com.lhy.common.shiro.service;

/**
 * 用户服务
 * @author luanhy
 *
 * @param 
 */
public interface PrincipalService {
	
	/**
	 * 根据用户id获取用户信息
	 * @param principal
	 * @return
	 */
	T select(String principal);
}


4.StatelessDefaultSubjectFactory无状态主题工厂

package com.lhy.common.shiro.subject;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;

/**
 * 无状态主题工厂
 * @author luanhy
 *
 */
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
	@Override
	public Subject createSubject(SubjectContext context) {
		//不创建session  
		context.setSessionCreationEnabled(false);
		return super.createSubject(context);
	}
}



5.用户令牌管理接口
package com.lhy.common.shiro.token.helper;


/**
 * 用户令牌操作接口
 * @author luanhy
 *
 */
public interface UserTokenOperHelper {
	
	/**
	 * 根据用户编码获取令牌
	 * @param userCode
	 * @return
	 */
	public String getUserToken(String userCode);
	
	/**
	 * 更新令牌, 每次获取令牌成功时更新令牌失效时间
	 * @param userCode
	 * @param token
	 * @param seconds
	 */
	public void updateUserToken(String userCode,String token,long seconds);
	
	/**
	 * 删除令牌
	 * @param userCode
	 */
	public void deleteUserToken(String userCode);
	
}

6.ehcache用户令牌帮助类 ,其他缓存同样支持,实现UserTokenOperHelper接口,编码各自存取token逻辑即可

package com.lhy.common.shiro.token.helper;

import java.util.List;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

/**
 * ehcache用户令牌帮助类
 * @author luanhy
 *
 */
public class EhCacheUserTokenHelper implements UserTokenOperHelper{
	
	/**
	 * 对应ehcache.xml cache Name
	 */
	private String userTokenCacheName ="userTokenCache";
	
	/**
	 * ehcache缓存管理器
	 */
	private CacheManager cacheManager;
	
	public String getUserToken(String userCode){
		Cache cache = getUserTokenCache();
		if (cache == null) {
			return null;
		}else{
			Element element = cache.get(userCode);
			List keys = cache.getKeys();
			for (Object object : keys) {
				System.out.println(object);
			}
			if(element == null){
				return null;
			}else{
				Object objectValue = element.getObjectValue();
				if(objectValue == null){
					return null;
				}else{
					return (String)objectValue;
				}
			}
		}
	}
	
	public Cache getUserTokenCache(){
		Cache cache = cacheManager.getCache(userTokenCacheName);
		return cache;
	}
	
	public void updateUserToken(String userCode,String token,long seconds){
		Cache cache = getUserTokenCache();
		Element e = new Element(userCode, token);
		e.setTimeToLive(new Long(seconds).intValue());
		cache.put(e);
		List keys = cache.getKeys();
		for (Object object : keys) {
			System.out.println(object);
		}
	}
	
	public void deleteUserToken(String userCode){
		Cache cache = getUserTokenCache();
		cache.remove(userCode);
	}

	public String getUserTokenCacheName() {
		return userTokenCacheName;
	}

	public void setUserTokenCacheName(String userTokenCacheName) {
		this.userTokenCacheName = userTokenCacheName;
	}

	public CacheManager getCacheManager() {
		return cacheManager;
	}

	public void setCacheManager(CacheManager cacheManager) {
		this.cacheManager = cacheManager;
	}
	
	

}

7.TokenManager 对token进行操作的接口

package com.lhy.common.shiro.token.manager;

import com.lhy.common.shiro.token.StatelessToken;

/**
 * 对token进行操作的接口
 * @author luanhy
 */
public interface TokenManager {

    /**
     * 创建一个token关联上指定用户
     * @param userCode 指定用户的id
     * @return 生成的token
     */
    public StatelessToken createToken(String userCode);
    
    /**
     * 检查token是否有效
     * @param statelessToken
     * @return 是否有效
     */
    public boolean checkToken(StatelessToken statelessToken);
    
    
    /**
     * 检查身份是否有效
     * @param model token
     * @return 是否有效
     */
    public boolean check(String authentication);

    /**
     * 从字符串中解析token
     * @param authentication 加密后的字符串
     * @return
     */
    public StatelessToken getToken(String authentication);

    /**
     * 清除token
     * @param userCode 登录用户的id
     */
    public void deleteToken(String userCode);

}


9.token管理抽象类

package com.lhy.common.shiro.token.manager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import com.lhy.common.shiro.token.StatelessToken;
import com.lhy.common.shiro.token.helper.UserTokenOperHelper;

public abstract class AbstractTokenManager implements TokenManager{
	
	/**
	 * 失效时间 单位秒
	 */
	protected long expirateTime;
	
	protected final Logger logger = LoggerFactory.getLogger(AbstractTokenManager.class);
	
	protected String userTokenPrefix ="token_";
	
	protected UserTokenOperHelper userTokenOperHelper;
	
	//protected LoginFlagOperHelper loginFlagOperHelper;
	
	@Override
	public StatelessToken createToken(String userCode) {
		StatelessToken tokenModel = null;
		String token = userTokenOperHelper.getUserToken(userTokenPrefix+userCode);
		if(StringUtils.isEmpty(token)){
			token = createStringToken(userCode);
		}
		userTokenOperHelper.updateUserToken(userTokenPrefix+userCode, token, expirateTime);
		tokenModel = new StatelessToken(userCode, token);
		return tokenModel;
	}
	
	public abstract String createStringToken(String userCode);

	protected boolean checkMemoryToken(StatelessToken model) {
		if(model == null){
			return false;
		}
		String userCode = (String)model.getPrincipal();
		String credentials = (String)model.getCredentials();
		String token = userTokenOperHelper.getUserToken(userTokenPrefix+userCode);
		if (token == null || !credentials.equals(token)) {
			return false;
		}
		return true;
	}
	
	@Override
	public StatelessToken getToken(String authentication){
		if(StringUtils.isEmpty(authentication)){
			return null;
		}
		String[] au = authentication.split("_");
		if (au.length <=1) {
			return null;
		}
		String userCode = au[0];
		StringBuilder sb = new StringBuilder();
		for (int i = 1; i < au.length; i++) {
			sb.append(au[i]);
			if(i


10.token默认管理类

package com.lhy.common.shiro.token.manager.impl;


import java.util.UUID;


import com.lhy.common.shiro.token.StatelessToken;
import com.lhy.common.shiro.token.manager.AbstractTokenManager;


/**
 * 默认token管理实现类
 * @author luanhy
 *
 */
public class DefaultTokenManagerImpl extends AbstractTokenManager{
	
	@Override
	public String createStringToken(String userCode) {
		//创建简易的32为uuid
		return UUID.randomUUID().toString().replace("-", "");
	}


	@Override
	public boolean checkToken(StatelessToken model) {
		return super.checkMemoryToken(model);
	}


}


11具体权限服务 这里用到了springcache缓存和mybatis持久层 可以根据爱好各自实现

package com.lhy.auth.service;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.lhy.common.mapper.WxUserMapper;
import com.lhy.common.model.WxUser;
import com.lhy.common.shiro.service.AuthorizationService;
import com.lhy.common.shiro.service.PrincipalService;

/**  
 * Copyright: Copyright (c) 2017 wisedu
 * 
 * @ClassName: MyAuthService.java
 * @Description: 具体权限服务
 *
 * @version: v1.0.0
 * @author: hyluan
 * @date: 2017年5月9日 下午4:12:16 
 *
 * Modification History:
 * Date         Author          Version            Description
 *---------------------------------------------------------*
 * 2017年5月9日     hyluan           v1.0.0               修改原因
 */
@Service
@CacheConfig(cacheNames="role")
public class MyAuthService implements AuthorizationService {
	
		private final Logger logger = LoggerFactory.getLogger(this.getClass());

		@Autowired
		private WxUserMapper userMapper;

		@Override
		@Cacheable
		public List selectRoles(String principal) {
			List roles = new ArrayList();
			//从数据库获取权限并设置
			logger.info("add roles");
			if("admin".equals(principal)){
				roles.add("admin");
				roles.add("vistor");
			}
			return roles;
			
		}

	}

12.具体用户服务

package com.lhy.api;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.lhy.common.mapper.WxUserMapper;
import com.lhy.common.model.WxUser;
import com.lhy.common.shiro.service.PrincipalService;

@Service
@CacheConfig(cacheNames="wxUser")
public class UserService implements PrincipalService{
	
	@Autowired
	private WxUserMapper userMapper;
	
	@Cacheable
	public WxUser getUserByUserCode(String userCode){
		WxUser user = new WxUser();
		user.setUserCode(userCode);
		return userMapper.selectOne(user);
	}

	@Override
	public WxUser select(String principal) {
		return this.getUserByUserCode(principal);
	}
}


13.StatelessToken令牌类

package com.lhy.common.shiro.token;

import org.apache.shiro.authc.AuthenticationToken;

public class StatelessToken implements AuthenticationToken {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private String userCode;
	
	private String token;
	
	public StatelessToken(String userCode, String token){
		this.userCode = userCode;
		this.token = token;
	}
	

	@Override
	public Object getPrincipal() {
		return userCode;
	}

	@Override
	public Object getCredentials() {
		return token;
	}
	
	public String getUserCode() {
		return userCode;
	}

	public String getToken() {
		return token;
	}


}



14.登陆Controller

package com.lhy.api;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.lhy.common.model.WxUser;
import com.lhy.common.shiro.service.PrincipalService;
import com.lhy.common.shiro.token.StatelessToken;
import com.lhy.common.shiro.token.manager.TokenManager;
import com.lhy.common.shiro.util.RequestUtil;


@RestController
@RequestMapping("/login")
public class LoginController{
	
	@Autowired
	private PrincipalService principalService;
	
	@Autowired
	private TokenManager tokenManager;
	
	protected static final Logger logger = LoggerFactory.getLogger(LoginController.class);
	
	@RequestMapping(value = "",method = RequestMethod.POST)
	public StatelessToken login(String userCode, String password) {
		logger.info("userCode:"+userCode);
		WxUser usr = principalService.select(userCode);
		if (usr == null) {
			return new StatelessToken(userCode, "valid user");
		}
		if(!password.equals(usr.getPwd())){
			return new StatelessToken(userCode, "valid user password");
		}
		//成功穿件token返回给客户端保存
		StatelessToken createToken = tokenManager.createToken(userCode);
		return createToken;
	}
	
	@RequestMapping(value = "/logout",method = RequestMethod.GET)
	public String logout(HttpServletRequest request) {
		String authorization = RequestUtil.newInstance().getRequestHeader(request,"authorization");
		StatelessToken token = tokenManager.getToken(authorization);
		if(token!= null){
			tokenManager.deleteToken(token.getUserCode());
		}
		SecurityUtils.getSubject().logout();
		logger.info("用户登出");
		return "logout success";
	}
	
	
}



14,前台login-notsafe.html登陆方法:用到了jquery.easyui插件

function submit() {
			var userCode =$("#userCode").val();
			var password =$("#password").val();
			$("#ff").form("submit", {
				url : contextPath + "/login",
				onSubmit : function(param) {
					var ret = $(this).form('validate');
					if (ret) {
						$("#submit").linkbutton("disable");
					}
					return ret;
				},
				success : function(data) {
					$('#submit').linkbutton('enable');
					data = JSON.parse(data);
					if (data.token.indexOf("valid user") < 0) {
						//把用户编码和token保存在sessionStorage
				   		sessionStorage.setItem('userCode',data.userCode);
				   		sessionStorage.setItem('authorization',data.token);
				   		location.href=contextPath+"/html/user-notsafe.html";
					} else {
						$.messager.alert('提示', '账号或密码错误', 'info');
					}
				}
			});
		}

15,user-notesafe.html 

api/users/admin3是一个简单的restful api接口获取用户

$(function() {
				
		$.ajax({
			url : contextPath+'/api/users/admin3',
			type : 'get',
			dataType : 'json',
			success : function(data) {
				alert(JSON.stringify(data));
			},
			error : function(e) {
				alert(e.responseText);
			}
		});
		
		$("#logout").click(function(){
			logOut();
		});
//使用ajaxSetup 统一设置请求头
$.ajaxSetup({
		cache: false,
	    contentType:"application/x-www-form-urlencoded;charset=utf-8",  
	   	beforeSend: function (xhr) {
	   		var authorization = localStorage.getItem('authorization');
	   		var userCode =localStorage.getItem('userCode');
	   		xhr.setRequestHeader("authorization", userCode+"_"+authorization);
	   	},
	    complete:function(XMLHttpRequest,textStatus){  
	    }  
	}); 
})
function logOut(){$.ajax({url : contextPath+'/login/logout',type : 'get',success : function(data) {alert(JSON.stringify(data));
				这里不删除也没有关系,服务器端已经失效了该token
				//sessionStorage.removeItem('userCode');
				//sessionStorage.removeItem('authorization');
				location.href=contextPath+"/html/login-notsafe.html";
			},
			error : function(e) {
				alert(e.responseText);
			}
		});
	}



验证方式如下:
登录页面
SpringBoot集成篇(一)无状态shiro_第1张图片

完成后跳转到user-notsafe.html 正常情况下能获取到admin3用户信息。
SpringBoot集成篇(一)无状态shiro_第2张图片

登出后访问user-notsafe.html,已无法访问
SpringBoot集成篇(一)无状态shiro_第3张图片

即实现了springboot和无状态shiro的集成


后记

此种鉴权方式 token是从服务端返回给客户端,并且在客户端浏览器中保留,只要拦截到了该token,任意一台客户端端在请求中设置token,就可以正常的请求数据,这是非常不安全的,生产环境不建议这么使用。细心的朋友可能已经发现, shiroConfig tokenManager方法中引入了一段注释RaFilterJwtTokenManagerImpl 使用了国际规范的jwt token认证,通过token+共享密钥+黑名单方式控制鉴权,只要没有密钥,token还是很难破解的。下一篇博客讲讲解基于jwt的安全性的spring-boot shiro token鉴权。



你可能感兴趣的:(web开发)