最近和别的软件集成项目,需要提供给别人接口来进行数据传输,发现给他token后并不能访问我的接口,拿postman试了下还真是不行。检查代码发现项目的shiro配置是通过session会话来校验信息的 ,我之前一直是前后端自己写,用浏览器来调试的程序所以没发现这个问题。
浏览器请求头的cookie带着jessionid是可以正常访问接口的
那要和别的项目集成,他那边又不是通过浏览器,咋办呢,我这边改造吧,兼容token和session不就行了,下面直接贴改造后的完整代码。
pom加依赖
<dependency> <groupid>org.crazycake</groupid> <artifactid>shiro-redis</artifactid> <version>2.4.2.1-release</version> </dependency> <dependency> <groupid>org.apache.shiro</groupid> <artifactid>shiro-all</artifactid> <version>1.3.2</version> </dependency> <dependency> <groupid>com.auth0</groupid> <artifactid>java-jwt</artifactid> <version>3.2.0</version> </dependency>
1.jwttoken重写token类型
package com.mes.common.token; import com.mes.module.user.dto.sysuserdto; import lombok.data; import org.apache.shiro.authc.hostauthenticationtoken; import org.apache.shiro.authc.remembermeauthenticationtoken; import org.apache.shiro.authc.usernamepasswordtoken; @data public class jwttoken implements hostauthenticationtoken, remembermeauthenticationtoken { private string token; private char[] password; private boolean rememberme = false; private string host; public jwttoken(string token){ this.token = token; } @override public string gethost() { return null; } @override public boolean isrememberme() { return false; } @override public object getprincipal() { return token; } @override public object getcredentials() { return token; } }
2.自定义过滤器 jwtfilter
package com.mes.common.shiro; import com.alibaba.fastjson.json; import com.auth0.jwt.interfaces.claim; import com.mes.common.token.jwttoken; import com.mes.common.utils.jwtutils; import com.mes.common.utils.result; import org.apache.commons.lang3.stringutils; import org.apache.shiro.securityutils; import org.apache.shiro.authc.authenticationexception; import org.apache.shiro.authc.authenticationtoken; import org.apache.shiro.subject.subject; import org.apache.shiro.web.filter.authc.authenticatingfilter; import org.apache.shiro.web.util.webutils; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.http.httpstatus; import org.springframework.web.bind.annotation.requestmethod; import javax.servlet.servletrequest; import javax.servlet.servletresponse; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import java.io.ioexception; import java.io.printwriter; import java.util.map; /** * @description 自定义过滤器 * @date 2021/8/18 **/ public class jwtfilter extends authenticatingfilter { private static final logger log = loggerfactory.getlogger(jwtfilter.class); @override protected authenticationtoken createtoken(servletrequest servletrequest, servletresponse servletresponse) throws exception { httpservletrequest request = (httpservletrequest) servletrequest; string token = request.getheader("token"); if (token == null){ return null; } return new jwttoken(token); } /** * 拦截校验 没有登录的情况下会走此方法 * @param servletrequest * @param servletresponse * @return * @throws exception */ @override protected boolean onaccessdenied(servletrequest servletrequest, servletresponse servletresponse) throws exception { httpservletrequest request = (httpservletrequest) servletrequest; httpservletresponse response = (httpservletresponse) servletresponse; string token = request.getheader("token"); response.setcontenttype("application/json;charset=utf-8"); response.setheader("access-control-allow-credentials", "true"); response.setheader("access-control-allow-origin", request.getheader("origin")); response.setheader("access-control-allow-methods", "get,put,delete,update,options"); response.setheader("access-control-allow-headers", request.getheader("access-control-request-headers")); subject subject = getsubject(servletrequest,servletresponse); if (!subject.isauthenticated()){ // 未登录 printwriter writer = response.getwriter(); writer.print(json.tojsonstring(new result<>().setcode(402).setmsg("请先登录"))); return false; } if (stringutils.isempty(token)){ printwriter writer = response.getwriter(); writer.print(json.tojsonstring(new result<>().setcode(402).setmsg("请先登录"))); return false; }else { // 校验jwt try { map<string, claim> claimmap = jwtutils.verifytoken(token); } catch (exception e) { e.printstacktrace(); printwriter writer = response.getwriter(); writer.write(json.tojsonstring(new result<>().setcode(402).setmsg("登录失效,请重新登录"))); return false; } return executelogin(servletrequest, servletresponse); } } @override protected boolean onloginfailure(authenticationtoken token, authenticationexception e, servletrequest request, servletresponse response) { httpservletresponse httpservletresponse = (httpservletresponse) response; throwable throwable = e.getcause() == null ? e : e.getcause(); result result = new result().err().setmsg(e.getmessage()); string json = json.tojsonstring(result); try { httpservletresponse.getwriter().print(json); } catch (ioexception ioexception) { } return false; } /** * 跨域支持 * @param servletrequest * @param response * @return * @throws exception */ @override protected boolean prehandle(servletrequest servletrequest, servletresponse response) throws exception { httpservletrequest httprequest = webutils.tohttp(servletrequest); httpservletresponse httpresponse = webutils.tohttp(response); if (httprequest.getmethod().equals(requestmethod.options.name())) { httpresponse.setheader("access-control-allow-credentials", "true"); httpresponse.setheader("access-control-allow-origin", httprequest.getheader("origin")); httpresponse.setheader("access-control-allow-methods", "get,put,delete,update,options"); httpresponse.setheader("access-control-allow-headers", httprequest.getheader("access-control-request-headers")); system.out.println(httprequest.getheader("origin")); system.out.println(httprequest.getmethod()); system.out.println(httprequest.getheader("access-control-request-headers")); httpresponse.setstatus(httpstatus.ok.value()); return false; } httpservletrequest request = (httpservletrequest) servletrequest; string token = request.getheader("token"); if (token != null) { try { // map<string, claim> claimmap = jwtutils.verifytoken(token); // string authtoken = claimmap.get("token").asstring(); jwttoken jwttoken = new jwttoken(token); subject subject = securityutils.getsubject(); subject.login(jwttoken); return true; } catch (exception e) { e.printstacktrace(); log.error("token失效,请重新登录"); response.getwriter().print(json.tojsonstring(new result<>().setcode(402).setmsg("token失效,请重新登录"))); } return false; }else { // session方式 return super.prehandle(servletrequest, response); } } /* protected boolean prehandle(servletrequest request, servletresponse response) throws exception { httpservletrequest httprequest = webutils.tohttp(request); httpservletresponse httpresponse = webutils.tohttp(response); if (httprequest.getmethod().equals(requestmethod.options.name())) { httpresponse.setheader("access-control-allow-credentials", "true"); httpresponse.setheader("access-control-allow-origin", httprequest.getheader("origin")); httpresponse.setheader("access-control-allow-methods", "get,put,delete,update,options"); httpresponse.setheader("access-control-allow-headers", httprequest.getheader("access-control-request-headers")); system.out.println(httprequest.getheader("origin")); system.out.println(httprequest.getmethod()); system.out.println(httprequest.getheader("access-control-request-headers")); httpresponse.setstatus(httpstatus.ok.value()); return false; } return super.prehandle(request, response); }*/ }
3.配置过滤器 shirofilterregisterconfig
package com.mes.common.config; import com.mes.common.shiro.jwtfilter; import org.springframework.boot.web.servlet.filterregistrationbean; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; /** * @description todo * @date 2021/8/19 **/ @configuration public class shirofilterregisterconfig { @bean public filterregistrationbean shirologinfilteregistration(jwtfilter filter) { filterregistrationbean registration = new filterregistrationbean(filter); registration.setenabled(false); return registration; } }
4. shiroconfig
package com..mes.common.config; import com.baomidou.mybatisplus.extension.api.r; import com..mes.common.constant.exptime; import com..mes.common.realm.myrealm; import com..mes.common.shiro.jwtfilter; import com..mes.common.shiro.mycredentialsmatcher; import org.apache.shiro.session.mgt.sessionmanager; import org.apache.shiro.spring.web.shirofilterfactorybean; import org.apache.shiro.web.mgt.defaultwebsecuritymanager; import org.apache.shiro.web.session.mgt.defaultwebsessionmanager; import org.crazycake.shiro.rediscachemanager; import org.crazycake.shiro.redismanager; import org.crazycake.shiro.redissessiondao; import org.springframework.beans.factory.annotation.autowired; import org.springframework.beans.factory.annotation.value; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import javax.servlet.filter; import java.util.hashmap; import java.util.linkedhashmap; import java.util.map; /** * @description shiro配置 * @date 2021/8/18 **/ @configuration public class shiroconfig { @autowired private myrealm myrealm; @autowired private mycredentialsmatcher mycredentialsmatcher; @value("${spring.redis.host}") private string redishost; @value("${spring.redis.port}") private integer redisport; @value("${spring.redis.timeout}") private integer redistimeout; // @bean // public defaultwebsessionmanager sessionmanager(@value("${globalsessiontimeout:3600}") long globalsessiontimeout, redismanager c){ // defaultwebsessionmanager sessionmanager = new defaultwebsessionmanager(); // sessionmanager.setsessionvalidationschedulerenabled(true); // sessionmanager.setsessionidurlrewritingenabled(false); // sessionmanager.setsessionvalidationinterval(globalsessiontimeout * 1000); // sessionmanager.setglobalsessiontimeout(globalsessiontimeout * 1000); // sessionmanager.setsessiondao(redissessiondao(c)); // return sessionmanager; // } // @configurationproperties(prefix="spring.redis") // @bean // public redismanager redismanager() { // return new redismanager(); // } // @bean // public redissessiondao redissessiondao(redismanager redismanager) { // redissessiondao redissessiondao = new redissessiondao(); // redissessiondao.setredismanager(redismanager); // return redissessiondao; // } // @bean // public static defaultadvisorautoproxycreator getdefaultadvisorautoproxycreator(){ // // defaultadvisorautoproxycreator defaultadvisorautoproxycreator=new defaultadvisorautoproxycreator(); // defaultadvisorautoproxycreator.setuseprefix(true); // // return defaultadvisorautoproxycreator; // } @bean public defaultwebsecuritymanager getdefaultwebsecuritymanager(sessionmanager sessionmanager, rediscachemanager rediscachemanager){ defaultwebsecuritymanager defaultwebsecuritymanager = new defaultwebsecuritymanager(); myrealm.setcredentialsmatcher(mycredentialsmatcher); defaultwebsecuritymanager.setrealm(myrealm); defaultwebsecuritymanager.setsessionmanager(sessionmanager); defaultwebsecuritymanager.setcachemanager(rediscachemanager); return defaultwebsecuritymanager; } @bean public shirofilterfactorybean getshirofilterfactorybean(defaultwebsecuritymanager defaultwebsecuritymanager,jwtfilter jwtfilter){ shirofilterfactorybean shirofilterfactorybean = new shirofilterfactorybean(); shirofilterfactorybean.setsecuritymanager(defaultwebsecuritymanager); // jwtfilter jwtfilter = new jwtfilter(); map<string, filter> filtermap = new hashmap<>(); filtermap.put("jwt",jwtfilter); shirofilterfactorybean.setfilters(filtermap); map<string,string> map = new linkedhashmap<>(); map.put("/sys/user/login","anon"); map.put("/swagger-ui.html**", "anon"); map.put("/v2/api-docs", "anon"); map.put("/swagger-resources/**", "anon"); map.put("/webjars/**", "anon"); map.put("/img/**","anon"); map.put("/fastdfs/**","anon"); map.put("/**","jwt"); //取消就不会拦截 shirofilterfactorybean.setfilterchaindefinitionmap(map); // shirofilterfactorybean.setloginurl("http://192.168.18.17:3000"); return shirofilterfactorybean; } @bean public jwtfilter getjwtfilter(){ return new jwtfilter(); } /** * 配置shiro redismanager * 使用的是shiro-redis开源插件 * @return */ @bean public redismanager redismanager() { redismanager redismanager = new redismanager(); redismanager.sethost(redishost); redismanager.setport(redisport); redismanager.setexpire(math.tointexact(exptime.exptime));// 配置缓存过期时间 redismanager.settimeout(redistimeout); return redismanager; } @bean public redissessiondao redissessiondao(redismanager redismanager) { // redissessiondao redissessiondao = new redissessiondao(); redissessiondao redissessiondao = new redissessiondao(); redissessiondao.setredismanager(redismanager); return redissessiondao; } /** * shiro session的管理 */ @bean public defaultwebsessionmanager redissessionmanager(redissessiondao redissessiondao) { defaultwebsessionmanager sessionmanager = new defaultwebsessionmanager(); sessionmanager.setsessiondao(redissessiondao); return sessionmanager; } @bean public rediscachemanager rediscachemanager(redismanager redismanager) { rediscachemanager rediscachemanager = new rediscachemanager(); rediscachemanager.setredismanager(redismanager); return rediscachemanager; } // @bean // public filterregistrationbean shirologinfilteregistration(jwtfilter filter) { // filterregistrationbean registration = new filterregistrationbean(filter); // registration.setenabled(false); // return registration; // } }
5.自定义认证逻辑 myrealm
package com.mes.common.realm; import com.auth0.jwt.interfaces.claim; import com.mes.common.token.jwttoken; import com.mes.common.utils.jwtutils; import com.mes.module.user.dto.sysuserdto; import com.mes.module.user.service.sysuserservice; import lombok.sneakythrows; import org.apache.shiro.authc.*; 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 org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.component; import java.util.hashmap; import java.util.map; /** * @description 授权 * @date 2021/8/18 **/ @component public class myrealm extends authorizingrealm { @autowired private sysuserservice sysuserservice; @override public boolean supports(authenticationtoken token) { return token instanceof jwttoken; } @override protected authorizationinfo dogetauthorizationinfo(principalcollection principalcollection) { string username = (string) principalcollection.iterator().next(); simpleauthorizationinfo info = new simpleauthorizationinfo(); return info; } @sneakythrows @override protected authenticationinfo dogetauthenticationinfo(authenticationtoken authenticationtoken) throws authenticationexception { jwttoken jwttoken = (jwttoken) authenticationtoken; string token = (string) jwttoken.getprincipal(); map<string, claim> claimmap = jwtutils.verifytoken(token); string username = claimmap.get("name").asstring(); map<string,object> params = new hashmap<>(); params.put("username", username); sysuserdto userdto = sysuserservice.getone(params); if (userdto == null){ return null; } // return new simpleauthenticationinfo(userdto,userdto.getpassword(),getname()); return new simpleauthenticationinfo(userdto,jwttoken,getname()); } }
6. token工具类
package com.mes.common.utils; import com.auth0.jwt.jwt; import com.auth0.jwt.jwtverifier; import com.auth0.jwt.algorithms.algorithm; import com.auth0.jwt.interfaces.claim; import com.auth0.jwt.interfaces.decodedjwt; import com.mes.common.constant.exptime; import io.jsonwebtoken.claims; import io.jsonwebtoken.jwts; import lombok.data; import javax.xml.bind.datatypeconverter; import java.io.unsupportedencodingexception; import java.util.date; import java.util.hashmap; import java.util.map; @data public class jwtutils { /** * 加密的秘钥,相当于服务器私钥,一定保管好,不能泄露 */ private static final string secret = "secret"; /** * token的有效时间,不需要自己验证失效,当失效后,会自动抛出异常 */ public static final long exptime = exptime.exptime; public static string createtoken(long id, string name, long loginid) throws unsupportedencodingexception { map<string, object> map = new hashmap<>(); map.put("alg", "hs256"); map.put("typ", "jwt"); string token = jwt.create() .withheader(map) .withclaim("id", id) .withclaim("name", name) .withclaim("loginid", loginid) .withexpiresat(new date(system.currenttimemillis() + exptime)) .withissuedat(new date()) .sign(algorithm.hmac256(secret)); return token; } public static map<string, claim> verifytoken(string token) throws unsupportedencodingexception { jwtverifier verifier = jwt.require(algorithm.hmac256(secret)).build(); decodedjwt jwt = null; try { jwt = verifier.verify(token); } catch (exception e) { throw new runtimeexception("登录凭证已过期,请重新登录"); } return jwt.getclaims(); } public static map<string, claim> getclaims(string token) throws unsupportedencodingexception { jwtverifier verifier = jwt.require(algorithm.hmac256(secret)).build(); decodedjwt jwt = null; jwt = verifier.verify(token); return jwt.getclaims(); } }
7.密码验证器
package com.mes.common.shiro; import com.mes.common.token.jwttoken; import com.mes.common.utils.commonsutils; import com.mes.module.user.dto.sysuserdto; import com.mes.module.user.service.sysuserservice; import org.apache.shiro.authc.authenticationinfo; import org.apache.shiro.authc.authenticationtoken; import org.apache.shiro.authc.credential.simplecredentialsmatcher; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.component; import java.util.hashmap; import java.util.map; /** * @description 密码验证器 * @date 2021/8/18 **/ @component public class mycredentialsmatcher extends simplecredentialsmatcher { @autowired private sysuserservice sysuserservice; @override public boolean docredentialsmatch(authenticationtoken token, authenticationinfo info) { jwttoken jwttoken = (jwttoken) token; if (jwttoken.getpassword() == null){ return true; } string inpassword = new string(jwttoken.getpassword()); sysuserdto dto = (sysuserdto) info.getprincipals(); string username = dto.getusername(); string dbpassword = string.valueof(info.getcredentials()); map<string,object> params = new hashmap<>(); params.put("username",username); sysuserdto dbuser = sysuserservice.getone(params); string salt = dbuser.getsalt(); if (commonsutils.encryptpassword(inpassword,salt).equals(dbpassword)){ return true; }else { return false; } } }
8.总结
在spring boot, shiro和jwt的项目中,可以同时使用session和token进行身份验证和授权,但通常token用于无状态的restful api,而session用于长连接的情况,如web应用。
发表评论