SpringBoot集成篇(一)无状态shiro

来源:互联网 发布:社交软件英语 编辑:程序博客网 时间:2024/05/21 10:36

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版本

<!-- shiro --><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-spring</artifactId>    <version>1.3.2</version></dependency>


代码实现

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 * */@Configurationpublic 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<String,Filter> filters = new HashMap<String,Filter>();          //无需增加 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<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();                    //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生命周期,<br>    *   该过滤器会执行其他过滤器拦截过的路径    * @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=helloworldshiro.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);@Autowiredprivate TokenManager tokenManager;public TokenManager getTokenManager() {return tokenManager;}public void setTokenManager(TokenManager tokenManager) {this.tokenManager = tokenManager;}@Overrideprotected 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;}// 获取无状态TokenStatelessToken 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;}@Overrideprotected 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;@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof StatelessToken;}private AuthorizationService authorizationService;@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //根据用户名查找角色,请根据需求实现  String userCode = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();List<String> selectRoles = authorizationService.selectRoles(userCode);authorizationInfo.addRoles(selectRoles);return authorizationInfo;}@Overrideprotected 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 <T> */public interface PrincipalService<T> {/** * 根据用户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 {@Overridepublic 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;@Overridepublic 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;}@Overridepublic 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<au.length-1){sb.append("_");}}return new StatelessToken(userCode, sb.toString());}@Overridepublic boolean check(String authentication) {StatelessToken token = getToken(authentication);if(token == null){return false;}return checkMemoryToken(token);}@Overridepublic void deleteToken(String userCode) {userTokenOperHelper.deleteUserToken(userTokenPrefix+userCode);}public long getExpirateTime() {return expirateTime;}public void setExpirateTime(long expirateTime) {this.expirateTime = expirateTime;}public UserTokenOperHelper getUserTokenOperHelper() {return userTokenOperHelper;}public void setUserTokenOperHelper(UserTokenOperHelper userTokenOperHelper) {this.userTokenOperHelper = userTokenOperHelper;}//public LoginFlagOperHelper getLoginFlagOperHelper() {//return loginFlagOperHelper;//}//public void setLoginFlagOperHelper(LoginFlagOperHelper loginFlagOperHelper) {//this.loginFlagOperHelper = loginFlagOperHelper;//}}


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{@Overridepublic String createStringToken(String userCode) {//创建简易的32为uuidreturn UUID.randomUUID().toString().replace("-", "");}@Overridepublic 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());@Autowiredprivate WxUserMapper userMapper;@Override@Cacheablepublic List<String> selectRoles(String principal) {List<String> roles = new ArrayList<String>();//从数据库获取权限并设置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<WxUser>{@Autowiredprivate WxUserMapper userMapper;@Cacheablepublic WxUser getUserByUserCode(String userCode){WxUser user = new WxUser();user.setUserCode(userCode);return userMapper.selectOne(user);}@Overridepublic 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;}@Overridepublic Object getPrincipal() {return userCode;}@Overridepublic 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{@Autowiredprivate PrincipalService<WxUser> principalService;@Autowiredprivate 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);}});}



验证方式如下:
登录页面


完成后跳转到user-notsafe.html 正常情况下能获取到admin3用户信息。


登出后访问user-notsafe.html,已无法访问


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


后记

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



1 0
原创粉丝点击