最近和别的软件集成项目,需要提供给别人接口来进行数据传输,发现给他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应用。
发表评论