renren-fast后端源码参考-配置和对应工具
程序员文章站
2022-06-24 11:18:20
1. renren fast后端源码参考 配置和对应工具 1.1. 前言 1. 是个开源的前后端分离快速开放平台,没有自己框架的同学可以直接使用它的,而我打算浏览一遍它的代码,提取一些好用的模块和功能结合自己的框架 2. 这里我会罗列所有值得参考的功能点,可能有点多,那就分几块罗列 3. 项目 "地 ......
1. renren-fast后端源码参考-配置和对应工具
1.1. 前言
-
renren-fast
是个开源的前后端分离快速开放平台,没有自己框架的同学可以直接使用它的,而我打算浏览一遍它的代码,提取一些好用的模块和功能结合自己的框架 - 这里我会罗列所有值得参考的功能点,可能有点多,那就分几块罗列
- 项目
- 由于renren本身的文档是需要购买才能观看,但实际上源码难度还是蛮低的,可以直接分模块引用需要的代码,参考我一下的模块划分
1.2. 代码
这里的代码提取是为了方便单独模块的集成
1.2.1. xss
- xss配置
import org.springframework.boot.web.servlet.filterregistrationbean; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.web.filter.delegatingfilterproxy; import javax.servlet.dispatchertype; /** * filter配置 */ @configuration public class filterconfig { @bean public filterregistrationbean shirofilterregistration() { filterregistrationbean registration = new filterregistrationbean(); registration.setfilter(new delegatingfilterproxy("shirofilter")); //该值缺省为false,表示生命周期由springapplicationcontext管理,设置为true则表示由servletcontainer管理 registration.addinitparameter("targetfilterlifecycle", "true"); registration.setenabled(true); registration.setorder(integer.max_value - 1); registration.addurlpatterns("/*"); return registration; } @bean public filterregistrationbean xssfilterregistration() { filterregistrationbean registration = new filterregistrationbean(); registration.setdispatchertypes(dispatchertype.request); registration.setfilter(new xssfilter()); registration.addurlpatterns("/*"); registration.setname("xssfilter"); registration.setorder(integer.max_value); return registration; } }
/** * xss过滤 */ public class xssfilter implements filter { @override public void init(filterconfig config) throws servletexception { } public void dofilter(servletrequest request, servletresponse response, filterchain chain) throws ioexception, servletexception { xsshttpservletrequestwrapper xssrequest = new xsshttpservletrequestwrapper( (httpservletrequest) request); chain.dofilter(xssrequest, response); } @override public void destroy() { } }
- xsshttpservletrequestwrapper
import org.apache.commons.io.ioutils; import org.apache.commons.lang.stringutils; import org.springframework.http.httpheaders; import org.springframework.http.mediatype; import javax.servlet.readlistener; import javax.servlet.servletinputstream; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletrequestwrapper; import java.io.bytearrayinputstream; import java.io.ioexception; import java.util.linkedhashmap; import java.util.map; /** * xss过滤处理 * * @author mark sunlightcs@gmail.com */ public class xsshttpservletrequestwrapper extends httpservletrequestwrapper { //没被包装过的httpservletrequest(特殊场景,需要自己过滤) httpservletrequest orgrequest; //html过滤 private final static htmlfilter htmlfilter = new htmlfilter(); public xsshttpservletrequestwrapper(httpservletrequest request) { super(request); orgrequest = request; } @override public servletinputstream getinputstream() throws ioexception { //非json类型,直接返回 if(!mediatype.application_json_value.equalsignorecase(super.getheader(httpheaders.content_type))){ return super.getinputstream(); } //为空,直接返回 string json = ioutils.tostring(super.getinputstream(), "utf-8"); if (stringutils.isblank(json)) { return super.getinputstream(); } //xss过滤 json = xssencode(json); final bytearrayinputstream bis = new bytearrayinputstream(json.getbytes("utf-8")); return new servletinputstream() { @override public boolean isfinished() { return true; } @override public boolean isready() { return true; } @override public void setreadlistener(readlistener readlistener) { } @override public int read() throws ioexception { return bis.read(); } }; } @override public string getparameter(string name) { string value = super.getparameter(xssencode(name)); if (stringutils.isnotblank(value)) { value = xssencode(value); } return value; } @override public string[] getparametervalues(string name) { string[] parameters = super.getparametervalues(name); if (parameters == null || parameters.length == 0) { return null; } for (int i = 0; i < parameters.length; i++) { parameters[i] = xssencode(parameters[i]); } return parameters; } @override public map<string,string[]> getparametermap() { map<string,string[]> map = new linkedhashmap<>(); map<string,string[]> parameters = super.getparametermap(); for (string key : parameters.keyset()) { string[] values = parameters.get(key); for (int i = 0; i < values.length; i++) { values[i] = xssencode(values[i]); } map.put(key, values); } return map; } @override public string getheader(string name) { string value = super.getheader(xssencode(name)); if (stringutils.isnotblank(value)) { value = xssencode(value); } return value; } private string xssencode(string input) { return htmlfilter.filter(input); } /** * 获取最原始的request */ public httpservletrequest getorgrequest() { return orgrequest; } /** * 获取最原始的request */ public static httpservletrequest getorgrequest(httpservletrequest request) { if (request instanceof xsshttpservletrequestwrapper) { return ((xsshttpservletrequestwrapper) request).getorgrequest(); } return request; } }
- html过滤
public final class htmlfilter { /** regex flag union representing /si modifiers in php **/ private static final int regex_flags_si = pattern.case_insensitive | pattern.dotall; private static final pattern p_comments = pattern.compile("<!--(.*?)-->", pattern.dotall); private static final pattern p_comment = pattern.compile("^!--(.*)--$", regex_flags_si); private static final pattern p_tags = pattern.compile("<(.*?)>", pattern.dotall); private static final pattern p_end_tag = pattern.compile("^/([a-z0-9]+)", regex_flags_si); private static final pattern p_start_tag = pattern.compile("^([a-z0-9]+)(.*?)(/?)$", regex_flags_si); private static final pattern p_quoted_attributes = pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", regex_flags_si); private static final pattern p_unquoted_attributes = pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", regex_flags_si); private static final pattern p_protocol = pattern.compile("^([^:]+):", regex_flags_si); private static final pattern p_entity = pattern.compile("&#(\\d+);?"); private static final pattern p_entity_unicode = pattern.compile("&#x([0-9a-f]+);?"); private static final pattern p_encode = pattern.compile("%([0-9a-f]{2});?"); private static final pattern p_valid_entities = pattern.compile("&([^&;]*)(?=(;|&|$))"); private static final pattern p_valid_quotes = pattern.compile("(>|^)([^<]+?)(<|$)", pattern.dotall); private static final pattern p_end_arrow = pattern.compile("^>"); private static final pattern p_body_to_end = pattern.compile("<([^>]*?)(?=<|$)"); private static final pattern p_xml_content = pattern.compile("(^|>)([^<]*?)(?=>)"); private static final pattern p_stray_left_arrow = pattern.compile("<([^>]*?)(?=<|$)"); private static final pattern p_stray_right_arrow = pattern.compile("(^|>)([^<]*?)(?=>)"); private static final pattern p_amp = pattern.compile("&"); private static final pattern p_quote = pattern.compile("<"); private static final pattern p_left_arrow = pattern.compile("<"); private static final pattern p_right_arrow = pattern.compile(">"); private static final pattern p_both_arrows = pattern.compile("<>"); // @xxx could grow large... maybe use sesat's referencemap private static final concurrentmap<string,pattern> p_remove_pair_blanks = new concurrenthashmap<string, pattern>(); private static final concurrentmap<string,pattern> p_remove_self_blanks = new concurrenthashmap<string, pattern>(); /** set of allowed html elements, along with allowed attributes for each element **/ private final map<string, list<string>> vallowed; /** counts of open tags for each (allowable) html element **/ private final map<string, integer> vtagcounts = new hashmap<string, integer>(); /** html elements which must always be self-closing (e.g. "<img />") **/ private final string[] vselfclosingtags; /** html elements which must always have separate opening and closing tags (e.g. "<b></b>") **/ private final string[] vneedclosingtags; /** set of disallowed html elements **/ private final string[] vdisallowed; /** attributes which should be checked for valid protocols **/ private final string[] vprotocolatts; /** allowed protocols **/ private final string[] vallowedprotocols; /** tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />") **/ private final string[] vremoveblanks; /** entities allowed within html markup **/ private final string[] vallowedentities; /** flag determining whether comments are allowed in input string. */ private final boolean stripcomment; private final boolean encodequotes; private boolean vdebug = false; /** * flag determining whether to try to make tags when presented with "unbalanced" * angle brackets (e.g. "<b text </b>" becomes "<b> text </b>"). if set to false, * unbalanced angle brackets will be html escaped. */ private final boolean alwaysmaketags; /** default constructor. * */ public htmlfilter() { vallowed = new hashmap<>(); final arraylist<string> a_atts = new arraylist<string>(); a_atts.add("href"); a_atts.add("target"); vallowed.put("a", a_atts); final arraylist<string> img_atts = new arraylist<string>(); img_atts.add("src"); img_atts.add("width"); img_atts.add("height"); img_atts.add("alt"); vallowed.put("img", img_atts); final arraylist<string> no_atts = new arraylist<string>(); vallowed.put("b", no_atts); vallowed.put("strong", no_atts); vallowed.put("i", no_atts); vallowed.put("em", no_atts); vselfclosingtags = new string[]{"img"}; vneedclosingtags = new string[]{"a", "b", "strong", "i", "em"}; vdisallowed = new string[]{}; vallowedprotocols = new string[]{"http", "mailto", "https"}; // no ftp. vprotocolatts = new string[]{"src", "href"}; vremoveblanks = new string[]{"a", "b", "strong", "i", "em"}; vallowedentities = new string[]{"amp", "gt", "lt", "quot"}; stripcomment = true; encodequotes = true; alwaysmaketags = true; } /** set debug flag to true. otherwise use default settings. see the default constructor. * * @param debug turn debug on with a true argument */ public htmlfilter(final boolean debug) { this(); vdebug = debug; } /** map-parameter configurable constructor. * * @param conf map containing configuration. keys match field names. */ public htmlfilter(final map<string,object> conf) { assert conf.containskey("vallowed") : "configuration requires vallowed"; assert conf.containskey("vselfclosingtags") : "configuration requires vselfclosingtags"; assert conf.containskey("vneedclosingtags") : "configuration requires vneedclosingtags"; assert conf.containskey("vdisallowed") : "configuration requires vdisallowed"; assert conf.containskey("vallowedprotocols") : "configuration requires vallowedprotocols"; assert conf.containskey("vprotocolatts") : "configuration requires vprotocolatts"; assert conf.containskey("vremoveblanks") : "configuration requires vremoveblanks"; assert conf.containskey("vallowedentities") : "configuration requires vallowedentities"; vallowed = collections.unmodifiablemap((hashmap<string, list<string>>) conf.get("vallowed")); vselfclosingtags = (string[]) conf.get("vselfclosingtags"); vneedclosingtags = (string[]) conf.get("vneedclosingtags"); vdisallowed = (string[]) conf.get("vdisallowed"); vallowedprotocols = (string[]) conf.get("vallowedprotocols"); vprotocolatts = (string[]) conf.get("vprotocolatts"); vremoveblanks = (string[]) conf.get("vremoveblanks"); vallowedentities = (string[]) conf.get("vallowedentities"); stripcomment = conf.containskey("stripcomment") ? (boolean) conf.get("stripcomment") : true; encodequotes = conf.containskey("encodequotes") ? (boolean) conf.get("encodequotes") : true; alwaysmaketags = conf.containskey("alwaysmaketags") ? (boolean) conf.get("alwaysmaketags") : true; } private void reset() { vtagcounts.clear(); } private void debug(final string msg) { if (vdebug) { logger.getanonymouslogger().info(msg); } } //--------------------------------------------------------------- // my versions of some php library functions public static string chr(final int decimal) { return string.valueof((char) decimal); } public static string htmlspecialchars(final string s) { string result = s; result = regexreplace(p_amp, "&", result); result = regexreplace(p_quote, """, result); result = regexreplace(p_left_arrow, "<", result); result = regexreplace(p_right_arrow, ">", result); return result; } //--------------------------------------------------------------- /** * given a user submitted input string, filter out any invalid or restricted * html. * * @param input text (i.e. submitted by a user) than may contain html * @return "clean" version of input, with only valid, whitelisted html elements allowed */ public string filter(final string input) { reset(); string s = input; debug("************************************************"); debug(" input: " + input); s = escapecomments(s); debug(" escapecomments: " + s); s = balancehtml(s); debug(" balancehtml: " + s); s = checktags(s); debug(" checktags: " + s); s = processremoveblanks(s); debug("processremoveblanks: " + s); s = validateentities(s); debug(" validateentites: " + s); debug("************************************************\n\n"); return s; } public boolean isalwaysmaketags(){ return alwaysmaketags; } public boolean isstripcomments(){ return stripcomment; } private string escapecomments(final string s) { final matcher m = p_comments.matcher(s); final stringbuffer buf = new stringbuffer(); if (m.find()) { final string match = m.group(1); //(.*?) m.appendreplacement(buf, matcher.quotereplacement("<!--" + htmlspecialchars(match) + "-->")); } m.appendtail(buf); return buf.tostring(); } private string balancehtml(string s) { if (alwaysmaketags) { // // try and form html // s = regexreplace(p_end_arrow, "", s); s = regexreplace(p_body_to_end, "<$1>", s); s = regexreplace(p_xml_content, "$1<$2", s); } else { // // escape stray brackets // s = regexreplace(p_stray_left_arrow, "<$1", s); s = regexreplace(p_stray_right_arrow, "$1$2><", s); // // the last regexp causes '<>' entities to appear // (we need to do a lookahead assertion so that the last bracket can // be used in the next pass of the regexp) // s = regexreplace(p_both_arrows, "", s); } return s; } private string checktags(string s) { matcher m = p_tags.matcher(s); final stringbuffer buf = new stringbuffer(); while (m.find()) { string replacestr = m.group(1); replacestr = processtag(replacestr); m.appendreplacement(buf, matcher.quotereplacement(replacestr)); } m.appendtail(buf); s = buf.tostring(); // these get tallied in processtag // (remember to reset before subsequent calls to filter method) for (string key : vtagcounts.keyset()) { for (int ii = 0; ii < vtagcounts.get(key); ii++) { s += "</" + key + ">"; } } return s; } private string processremoveblanks(final string s) { string result = s; for (string tag : vremoveblanks) { if(!p_remove_pair_blanks.containskey(tag)){ p_remove_pair_blanks.putifabsent(tag, pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">")); } result = regexreplace(p_remove_pair_blanks.get(tag), "", result); if(!p_remove_self_blanks.containskey(tag)){ p_remove_self_blanks.putifabsent(tag, pattern.compile("<" + tag + "(\\s[^>]*)?/>")); } result = regexreplace(p_remove_self_blanks.get(tag), "", result); } return result; } private static string regexreplace(final pattern regex_pattern, final string replacement, final string s) { matcher m = regex_pattern.matcher(s); return m.replaceall(replacement); } private string processtag(final string s) { // ending tags matcher m = p_end_tag.matcher(s); if (m.find()) { final string name = m.group(1).tolowercase(); if (allowed(name)) { if (!inarray(name, vselfclosingtags)) { if (vtagcounts.containskey(name)) { vtagcounts.put(name, vtagcounts.get(name) - 1); return "</" + name + ">"; } } } } // starting tags m = p_start_tag.matcher(s); if (m.find()) { final string name = m.group(1).tolowercase(); final string body = m.group(2); string ending = m.group(3); //debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); if (allowed(name)) { string params = ""; final matcher m2 = p_quoted_attributes.matcher(body); final matcher m3 = p_unquoted_attributes.matcher(body); final list<string> paramnames = new arraylist<string>(); final list<string> paramvalues = new arraylist<string>(); while (m2.find()) { paramnames.add(m2.group(1)); //([a-z0-9]+) paramvalues.add(m2.group(3)); //(.*?) } while (m3.find()) { paramnames.add(m3.group(1)); //([a-z0-9]+) paramvalues.add(m3.group(3)); //([^\"\\s']+) } string paramname, paramvalue; for (int ii = 0; ii < paramnames.size(); ii++) { paramname = paramnames.get(ii).tolowercase(); paramvalue = paramvalues.get(ii); // debug( "paramname='" + paramname + "'" ); // debug( "paramvalue='" + paramvalue + "'" ); // debug( "allowed? " + vallowed.get( name ).contains( paramname ) ); if (allowedattribute(name, paramname)) { if (inarray(paramname, vprotocolatts)) { paramvalue = processparamprotocol(paramvalue); } params += " " + paramname + "=\"" + paramvalue + "\""; } } if (inarray(name, vselfclosingtags)) { ending = " /"; } if (inarray(name, vneedclosingtags)) { ending = ""; } if (ending == null || ending.length() < 1) { if (vtagcounts.containskey(name)) { vtagcounts.put(name, vtagcounts.get(name) + 1); } else { vtagcounts.put(name, 1); } } else { ending = " /"; } return "<" + name + params + ending + ">"; } else { return ""; } } // comments m = p_comment.matcher(s); if (!stripcomment && m.find()) { return "<" + m.group() + ">"; } return ""; } private string processparamprotocol(string s) { s = decodeentities(s); final matcher m = p_protocol.matcher(s); if (m.find()) { final string protocol = m.group(1); if (!inarray(protocol, vallowedprotocols)) { // bad protocol, turn into local anchor link instead s = "#" + s.substring(protocol.length() + 1, s.length()); if (s.startswith("#//")) { s = "#" + s.substring(3, s.length()); } } } return s; } private string decodeentities(string s) { stringbuffer buf = new stringbuffer(); matcher m = p_entity.matcher(s); while (m.find()) { final string match = m.group(1); final int decimal = integer.decode(match).intvalue(); m.appendreplacement(buf, matcher.quotereplacement(chr(decimal))); } m.appendtail(buf); s = buf.tostring(); buf = new stringbuffer(); m = p_entity_unicode.matcher(s); while (m.find()) { final string match = m.group(1); final int decimal = integer.valueof(match, 16).intvalue(); m.appendreplacement(buf, matcher.quotereplacement(chr(decimal))); } m.appendtail(buf); s = buf.tostring(); buf = new stringbuffer(); m = p_encode.matcher(s); while (m.find()) { final string match = m.group(1); final int decimal = integer.valueof(match, 16).intvalue(); m.appendreplacement(buf, matcher.quotereplacement(chr(decimal))); } m.appendtail(buf); s = buf.tostring(); s = validateentities(s); return s; } private string validateentities(final string s) { stringbuffer buf = new stringbuffer(); // validate entities throughout the string matcher m = p_valid_entities.matcher(s); while (m.find()) { final string one = m.group(1); //([^&;]*) final string two = m.group(2); //(?=(;|&|$)) m.appendreplacement(buf, matcher.quotereplacement(checkentity(one, two))); } m.appendtail(buf); return encodequotes(buf.tostring()); } private string encodequotes(final string s){ if(encodequotes){ stringbuffer buf = new stringbuffer(); matcher m = p_valid_quotes.matcher(s); while (m.find()) { final string one = m.group(1); //(>|^) final string two = m.group(2); //([^<]+?) final string three = m.group(3); //(<|$) m.appendreplacement(buf, matcher.quotereplacement(one + regexreplace(p_quote, """, two) + three)); } m.appendtail(buf); return buf.tostring(); }else{ return s; } } private string checkentity(final string preamble, final string term) { return ";".equals(term) && isvalidentity(preamble) ? '&' + preamble : "&" + preamble; } private boolean isvalidentity(final string entity) { return inarray(entity, vallowedentities); } private static boolean inarray(final string s, final string[] array) { for (string item : array) { if (item != null && item.equals(s)) { return true; } } return false; } private boolean allowed(final string name) { return (vallowed.isempty() || vallowed.containskey(name)) && !inarray(name, vdisallowed); } private boolean allowedattribute(final string name, final string paramname) { return allowed(name) && (vallowed.isempty() || vallowed.get(name).contains(paramname)); } }
1.2.2. shiro
shiro
需要引入pom
<dependency> <groupid>org.apache.shiro</groupid> <artifactid>shiro-core</artifactid> <version>1.4.0</version> </dependency> <dependency> <groupid>org.apache.shiro</groupid> <artifactid>shiro-spring</artifactid> <version>1.4.0</version> </dependency>
shiro
配置
import org.apache.shiro.mgt.securitymanager; import org.apache.shiro.spring.lifecyclebeanpostprocessor; import org.apache.shiro.spring.security.interceptor.authorizationattributesourceadvisor; import org.apache.shiro.spring.web.shirofilterfactorybean; import org.apache.shiro.web.mgt.defaultwebsecuritymanager; 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; /** * shiro配置 * */ @configuration public class shiroconfig { @bean("securitymanager") public securitymanager securitymanager(oauth2realm oauth2realm) { defaultwebsecuritymanager securitymanager = new defaultwebsecuritymanager(); securitymanager.setrealm(oauth2realm); securitymanager.setremembermemanager(null); return securitymanager; } @bean("shirofilter") public shirofilterfactorybean shirfilter(securitymanager securitymanager) { shirofilterfactorybean shirofilter = new shirofilterfactorybean(); shirofilter.setsecuritymanager(securitymanager); //oauth过滤 map<string, filter> filters = new hashmap<>(); filters.put("oauth2", new oauth2filter()); shirofilter.setfilters(filters); map<string, string> filtermap = new linkedhashmap<>(); filtermap.put("/webjars/**", "anon"); filtermap.put("/druid/**", "anon"); filtermap.put("/app/**", "anon"); filtermap.put("/sys/login", "anon"); filtermap.put("/swagger/**", "anon"); filtermap.put("/v2/api-docs", "anon"); filtermap.put("/swagger-ui.html", "anon"); filtermap.put("/swagger-resources/**", "anon"); filtermap.put("/captcha.jpg", "anon"); filtermap.put("/aaa.txt", "anon"); filtermap.put("/**", "oauth2"); shirofilter.setfilterchaindefinitionmap(filtermap); return shirofilter; } @bean("lifecyclebeanpostprocessor") public lifecyclebeanpostprocessor lifecyclebeanpostprocessor() { return new lifecyclebeanpostprocessor(); } @bean public authorizationattributesourceadvisor authorizationattributesourceadvisor(securitymanager securitymanager) { authorizationattributesourceadvisor advisor = new authorizationattributesourceadvisor(); advisor.setsecuritymanager(securitymanager); return advisor; } }
import com.google.gson.gson; import io.renren.common.utils.httpcontextutils; import io.renren.common.utils.r; import org.apache.commons.lang.stringutils; import org.apache.http.httpstatus; import org.apache.shiro.authc.authenticationexception; import org.apache.shiro.authc.authenticationtoken; import org.apache.shiro.web.filter.authc.authenticatingfilter; 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; /** * oauth2过滤器 * */ public class oauth2filter extends authenticatingfilter { @override protected authenticationtoken createtoken(servletrequest request, servletresponse response) throws exception { //获取请求token string token = getrequesttoken((httpservletrequest) request); if(stringutils.isblank(token)){ return null; } return new oauth2token(token); } @override protected boolean isaccessallowed(servletrequest request, servletresponse response, object mappedvalue) { if(((httpservletrequest) request).getmethod().equals(requestmethod.options.name())){ return true; } return false; } @override protected boolean onaccessdenied(servletrequest request, servletresponse response) throws exception { //获取请求token,如果token不存在,直接返回401 string token = getrequesttoken((httpservletrequest) request); if(stringutils.isblank(token)){ httpservletresponse httpresponse = (httpservletresponse) response; httpresponse.setheader("access-control-allow-credentials", "true"); httpresponse.setheader("access-control-allow-origin", httpcontextutils.getorigin()); string json = new gson().tojson(r.error(httpstatus.sc_unauthorized, "invalid token")); httpresponse.getwriter().print(json); return false; } return executelogin(request, response); } @override protected boolean onloginfailure(authenticationtoken token, authenticationexception e, servletrequest request, servletresponse response) { httpservletresponse httpresponse = (httpservletresponse) response; httpresponse.setcontenttype("application/json;charset=utf-8"); httpresponse.setheader("access-control-allow-credentials", "true"); httpresponse.setheader("access-control-allow-origin", httpcontextutils.getorigin()); try { //处理登录失败的异常 throwable throwable = e.getcause() == null ? e : e.getcause(); r r = r.error(httpstatus.sc_unauthorized, throwable.getmessage()); string json = new gson().tojson(r); httpresponse.getwriter().print(json); } catch (ioexception e1) { } return false; } /** * 获取请求的token */ private string getrequesttoken(httpservletrequest httprequest){ //从header中获取token string token = httprequest.getheader("token"); //如果header中不存在token,则从参数中获取token if(stringutils.isblank(token)){ token = httprequest.getparameter("token"); } return token; } }
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.set; /** * 认证 * */ @component public class oauth2realm extends authorizingrealm { @autowired private shiroservice shiroservice; @override public boolean supports(authenticationtoken token) { return token instanceof oauth2token; } /** * 授权(验证权限时调用) */ @override protected authorizationinfo dogetauthorizationinfo(principalcollection principals) { sysuserentity user = (sysuserentity)principals.getprimaryprincipal(); long userid = user.getuserid(); //用户权限列表 set<string> permsset = shiroservice.getuserpermissions(userid); simpleauthorizationinfo info = new simpleauthorizationinfo(); info.setstringpermissions(permsset); return info; } /** * 认证(登录时调用) */ @override protected authenticationinfo dogetauthenticationinfo(authenticationtoken token) throws authenticationexception { string accesstoken = (string) token.getprincipal(); //根据accesstoken,查询用户信息 sysusertokenentity tokenentity = shiroservice.querybytoken(accesstoken); //token失效 if(tokenentity == null || tokenentity.getexpiretime().gettime() < system.currenttimemillis()){ throw new incorrectcredentialsexception("token失效,请重新登录"); } //查询用户信息 sysuserentity user = shiroservice.queryuser(tokenentity.getuserid()); //账号锁定 if(user.getstatus() == 0){ throw new lockedaccountexception("账号已被锁定,请联系管理员"); } simpleauthenticationinfo info = new simpleauthenticationinfo(user, accesstoken, getname()); return info; } }
对应的用户设计
import com.baomidou.mybatisplus.annotation.tablefield; import com.baomidou.mybatisplus.annotation.tableid; import com.baomidou.mybatisplus.annotation.tablename; import io.renren.common.validator.group.addgroup; import io.renren.common.validator.group.updategroup; import lombok.data; import javax.validation.constraints.email; import javax.validation.constraints.notblank; import java.io.serializable; import java.util.date; import java.util.list; /** * 系统用户 * */ @data @tablename("sys_user") public class sysuserentity implements serializable { private static final long serialversionuid = 1l; /** * 用户id */ @tableid private long userid; /** * 用户名 */ @notblank(message="用户名不能为空", groups = {addgroup.class, updategroup.class}) private string username; /** * 密码 */ @notblank(message="密码不能为空", groups = addgroup.class) private string password; /** * 盐 */ private string salt; /** * 邮箱 */ @notblank(message="邮箱不能为空", groups = {addgroup.class, updategroup.class}) @email(message="邮箱格式不正确", groups = {addgroup.class, updategroup.class}) private string email; /** * 手机号 */ private string mobile; /** * 状态 0:禁用 1:正常 */ private integer status; /** * 角色id列表 */ @tablefield(exist=false) private list<long> roleidlist; /** * 创建者id */ private long createuserid; /** * 创建时间 */ private date createtime; }
/** * 系统用户token * */ @data @tablename("sys_user_token") public class sysusertokenentity implements serializable { private static final long serialversionuid = 1l; //用户id @tableid(type = idtype.input) private long userid; //token private string token; //过期时间 private date expiretime; //更新时间 private date updatetime; }
/** * shiro相关接口 * */ public interface shiroservice { /** * 获取用户权限列表 */ set<string> getuserpermissions(long userid); sysusertokenentity querybytoken(string token); /** * 根据用户id,查询用户 * @param userid */ sysuserentity queryuser(long userid); }
shiro
工具类
import org.apache.shiro.securityutils; import org.apache.shiro.session.session; import org.apache.shiro.subject.subject; /** * shiro工具类 * */ public class shiroutils { public static session getsession() { return securityutils.getsubject().getsession(); } public static subject getsubject() { return securityutils.getsubject(); } public static sysuserentity getuserentity() { return (sysuserentity)securityutils.getsubject().getprincipal(); } public static long getuserid() { return getuserentity().getuserid(); } public static void setsessionattribute(object key, object value) { getsession().setattribute(key, value); } public static object getsessionattribute(object key) { return getsession().getattribute(key); } public static boolean islogin() { return securityutils.getsubject().getprincipal() != null; } public static string getkaptcha(string key) { object kaptcha = getsessionattribute(key); if(kaptcha == null){ throw new rrexception("验证码已失效"); } getsession().removeattribute(key); return kaptcha.tostring(); } }
/** * 自定义异常 * */ public class rrexception extends runtimeexception { private static final long serialversionuid = 1l; private string msg; private int code = 500; public rrexception(string msg) { super(msg); this.msg = msg; } public rrexception(string msg, throwable e) { super(msg, e); this.msg = msg; } public rrexception(string msg, int code) { super(msg); this.msg = msg; this.code = code; } public rrexception(string msg, int code, throwable e) { super(msg, e); this.msg = msg; this.code = code; } public string getmsg() { return msg; } public void setmsg(string msg) { this.msg = msg; } public int getcode() { return code; } public void setcode(int code) { this.code = code; } }
1.2.3. cors跨域
@configuration public class corsconfig implements webmvcconfigurer { @override public void addcorsmappings(corsregistry registry) { registry.addmapping("/**") .allowedorigins("*") .allowcredentials(true) .allowedmethods("get", "post", "put", "delete", "options") .maxage(3600); } }
1.2.4. 验证码
- 使用了
kaptcha
<dependency> <groupid>com.github.axet</groupid> <artifactid>kaptcha</artifactid> <version>0.0.9</version> </dependency>
- 配置
import com.google.code.kaptcha.impl.defaultkaptcha; import com.google.code.kaptcha.util.config; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import java.util.properties; /** * 生成验证码配置 * */ @configuration public class kaptchaconfig { @bean public defaultkaptcha producer() { properties properties = new properties(); properties.put("kaptcha.border", "no"); properties.put("kaptcha.textproducer.font.color", "black"); properties.put("kaptcha.textproducer.char.space", "5"); properties.put("kaptcha.textproducer.font.names", "arial,courier,cmr10,宋体,楷体,微软雅黑"); config config = new config(properties); defaultkaptcha defaultkaptcha = new defaultkaptcha(); defaultkaptcha.setconfig(config); return defaultkaptcha; } }
- controller层
/** * 验证码 */ @getmapping("captcha.jpg") public void captcha(httpservletresponse response, string uuid)throws ioexception { response.setheader("cache-control", "no-store, no-cache"); response.setcontenttype("image/jpeg"); //获取图片验证码 bufferedimage image = syscaptchaservice.getcaptcha(uuid); servletoutputstream out = response.getoutputstream(); imageio.write(image, "jpg", out); ioutils.closequietly(out); }
//生成文字验证码 string code = producer.createtext(); //生成图片 bufferedimage image = producer.createimage(code);
1.2.5. mybatis-plus配置
- pom引入
<dependency> <groupid>com.baomidou</groupid> <artifactid>mybatis-plus-boot-starter</artifactid> <version>3.0.7.1</version> <exclusions> <exclusion> <groupid>com.baomidou</groupid> <artifactid>mybatis-plus-generator</artifactid> </exclusion> </exclusions> </dependency> <dependency> <groupid>com.baomidou</groupid> <artifactid>mybatis-plus</artifactid> <version>3.0.7.1</version> </dependency>
import com.baomidou.mybatisplus.core.injector.isqlinjector; import com.baomidou.mybatisplus.extension.injector.logicsqlinjector; import com.baomidou.mybatisplus.extension.plugins.paginationinterceptor; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; /** * mybatis-plus配置 * */ @configuration public class mybatisplusconfig { /** * 分页插件 */ @bean public paginationinterceptor paginationinterceptor() { return new paginationinterceptor(); } @bean public isqlinjector sqlinjector() { return new logicsqlinjector(); } }
1.2.6. redis配置
import org.springframework.beans.factory.annotation.autowired; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.data.redis.connection.redisconnectionfactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.serializer.stringredisserializer; /** * redis配置 */ @configuration public class redisconfig { @autowired private redisconnectionfactory factory; @bean public redistemplate<string, object> redistemplate() { redistemplate<string, object> redistemplate = new redistemplate<>(); redistemplate.setkeyserializer(new stringredisserializer()); redistemplate.sethashkeyserializer(new stringredisserializer()); redistemplate.sethashvalueserializer(new stringredisserializer()); redistemplate.setvalueserializer(new stringredisserializer()); redistemplate.setconnectionfactory(factory); return redistemplate; } @bean public hashoperations<string, string, object> hashoperations(redistemplate<string, object> redistemplate) { return redistemplate.opsforhash(); } @bean public valueoperations<string, string> valueoperations(redistemplate<string, string> redistemplate) { return redistemplate.opsforvalue(); } @bean public listoperations<string, object> listoperations(redistemplate<string, object> redistemplate) { return redistemplate.opsforlist(); } @bean public setoperations<string, object> setoperations(redistemplate<string, object> redistemplate) { return redistemplate.opsforset(); } @bean public zsetoperations<string, object> zsetoperations(redistemplate<string, object> redistemplate) { return redistemplate.opsforzset(); } }
- 对应的工具类
import com.google.gson.gson; import org.springframework.beans.factory.annotation.autowired; import org.springframework.data.redis.core.*; import org.springframework.stereotype.component; import java.util.concurrent.timeunit; /** * redis工具类 * */ @component public class redisutils { @autowired private redistemplate<string, object> redistemplate; @autowired private valueoperations<string, string> valueoperations; @autowired private hashoperations<string, string, object> hashoperations; @autowired private listoperations<string, object> listoperations; @autowired private setoperations<string, object> setoperations; @autowired private zsetoperations<string, object> zsetoperations; /** 默认过期时长,单位:秒 */ public final static long default_expire = 60 * 60 * 24; /** 不设置过期时长 */ public final static long not_expire = -1; private final static gson gson = new gson(); public void set(string key, object value, long expire){ valueoperations.set(key, tojson(value)); if(expire != not_expire){ redistemplate.expire(key, expire, timeunit.seconds); } } public void set(string key, object value){ set(key, value, default_expire); } public <t> t get(string key, class<t> clazz, long expire) { string value = valueoperations.get(key); if(expire != not_expire){ redistemplate.expire(key, expire, timeunit.seconds); } return value == null ? null : fromjson(value, clazz); } public <t> t get(string key, class<t> clazz) { return get(key, clazz, not_expire); } public string get(string key, long expire) { string value = valueoperations.get(key); if(expire != not_expire){ redistemplate.expire(key, expire, timeunit.seconds); } return value; } public string get(string key) { return get(key, not_expire); } public void delete(string key) { redistemplate.delete(key); } /** * object转成json数据 */ private string tojson(object object){ if(object instanceof integer || object instanceof long || object instanceof float || object instanceof double || object instanceof boolean || object instanceof string){ return string.valueof(object); } return gson.tojson(object); } /** * json数据,转成object */ private <t> t fromjson(string json, class<t> clazz){ return gson.fromjson(json, clazz); } }
其中gson
对象是来自qiniu-java-sdk
,不需要的可以剔除或者一般国内就用fastjson
- 切面,用来开启关闭redis缓存
/** * redis切面处理类 * */ @aspect @configuration public class redisaspect { private logger logger = loggerfactory.getlogger(getclass()); //是否开启redis缓存 true开启 false关闭 @value("${spring.redis.open: false}") private boolean open; @around("execution(* io.renren.common.utils.redisutils.*(..))") public object around(proceedingjoinpoint point) throws throwable { object result = null; if(open){ try{ result = point.proceed(); }catch (exception e){ logger.error("redis error", e); throw new rrexception("redis服务异常"); } } return result; } }
1.2.7. swagger配置
- pom
<dependency> <groupid>io.springfox</groupid> <artifactid>springfox-swagger2</artifactid> <version>2.7.0</version> </dependency> <dependency> <groupid>io.springfox</groupid> <artifactid>springfox-swagger-ui</artifactid> <version>2.7.0</version> </dependency>
- 配置
import io.swagger.annotations.apioperation; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.web.servlet.config.annotation.webmvcconfigurer; import springfox.documentation.builders.apiinfobuilder; import springfox.documentation.builders.pathselectors; import springfox.documentation.builders.requesthandlerselectors; import springfox.documentation.service.apiinfo; import springfox.documentation.service.apikey; import springfox.documentation.spi.documentationtype; import springfox.documentation.spring.web.plugins.docket; import springfox.documentation.swagger2.annotations.enableswagger2; import java.util.list; import static com.google.common.collect.lists.newarraylist; @configuration @enableswagger2 public class swaggerconfig implements webmvcconfigurer { @bean public docket createrestapi() { return new docket(documentationtype.swagger_2) .apiinfo(apiinfo()) .select() //加了apioperation注解的类,才生成接口文档 .apis(requesthandlerselectors.withmethodannotation(apioperation.class)) //包下的类,才生成接口文档 //.apis(requesthandlerselectors.basepackage("io.renren.controller")) .paths(pathselectors.any()) .build() .securityschemes(security()); } private apiinfo apiinfo() { return new apiinfobuilder() .title("人人开源") .description("renren-fast文档") .termsofserviceurl("https://www.renren.io") .version("3.0.0") .build(); } private list<apikey> security() { return newarraylist( new apikey("token", "token", "header") ); } }
1.2.8. 日志
- 通过注解记录日志
import com.google.gson.gson; import io.renren.common.annotation.syslog; import io.renren.common.utils.httpcontextutils; import io.renren.common.utils.iputils; import io.renren.modules.sys.entity.syslogentity; import io.renren.modules.sys.entity.sysuserentity; import io.renren.modules.sys.service.syslogservice; import org.apache.shiro.securityutils; import org.aspectj.lang.proceedingjoinpoint; import org.aspectj.lang.annotation.around; import org.aspectj.lang.annotation.aspect; import org.aspectj.lang.annotation.pointcut; import org.aspectj.lang.reflect.methodsignature; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.component; import javax.servlet.http.httpservletrequest; import java.lang.reflect.method; import java.util.date; /** * 系统日志,切面处理类 */ @aspect @component public class syslogaspect { @autowired private syslogservice syslogservice; @pointcut("@annotation(io.renren.common.annotation.syslog)") public void logpointcut() { } @around("logpointcut()") public object around(proceedingjoinpoint point) throws throwable { long begintime = system.currenttimemillis(); //执行方法 object result = point.proceed(); //执行时长(毫秒) long time = system.currenttimemillis() - begintime; //保存日志 savesyslog(point, time); return result; } private void savesyslog(proceedingjoinpoint joinpoint, long time) { methodsignature signature = (methodsignature) joinpoint.getsignature(); method method = signature.getmethod(); syslogentity syslog = new syslogentity(); syslog syslog = method.getannotation(syslog.class); if(syslog != null){ //注解上的描述 syslog.setoperation(syslog.value()); } //请求的方法名 string classname = joinpoint.gettarget().getclass().getname(); string methodname = signature.getname(); syslog.setmethod(classname + "." + methodname + "()"); //请求的参数 object[] args = joinpoint.getargs(); try{ string params = new gson().tojson(args); syslog.setparams(params); }catch (exception e){ } //获取request httpservletrequest request = httpcontextutils.gethttpservletrequest(); //设置ip地址 syslog.setip(iputils.getipaddr(request)); //用户名 string username = ((sysuserentity) securityutils.getsubject().getprincipal()).getusername(); syslog.setusername(username); syslog.settime(time); syslog.setcreatedate(new date()); //保存系统日志 syslogservice.save(syslog); } }
- 日志对应的bean
/** * 系统日志 * */ @data @tablename("sys_log") public class syslogentity implements serializable { private static final long serialversionuid = 1l; @tableid private long id; //用户名 private string username; //用户操作 private string operation; //请求方法 private string method; //请求参数 private string params; //执行时长(毫秒) private long time; //ip地址 private string ip; //创建时间 private date createdate; }
- 注解
/** * 系统日志注解 * */ @target(elementtype.method) @retention(retentionpolicy.runtime) @documented public @interface syslog { string value() default ""; }
1.2.9. 校验工具
import javax.validation.constraintviolation; import javax.validation.validation; import javax.validation.validator; import java.util.set; /** * hibernate-validator校验工具类 * * 参考文档:http://docs.jboss.org/hibernate/validator/5.4/reference/en-us/html_single/ */ public class validatorutils { private static validator validator; static { validator = validation.builddefaultvalidatorfactory().getvalidator(); } /** * 校验对象 * @param object 待校验对象 * @param groups 待校验的组 * @throws rrexception 校验不通过,则报rrexception异常 */ public static void validateentity(object object, class<?>... groups) throws rrexception { set<constraintviolation<object>> constraintviolations = validator.validate(object, groups); if (!constraintviolations.isempty()) { stringbuilder msg = new stringbuilder(); for(constraintviolation<object> constraint: constraintviolations){ msg.append(constraint.getmessage()).append("<br>"); } throw new rrexception(msg.tostring()); } } }
1.2.10. 防止sql注入工具
- 条件层过滤
/** * sql过滤 * */ public class sqlfilter { /** * sql注入过滤 * @param str 待验证的字符串 */ public static string sqlinject(string str){ if(stringutils.isblank(str)){ return null; } //去掉'|"|;|\字符 str = stringutils.replace(str, "'", ""); str = stringutils.replace(str, "\"", ""); str = stringutils.replace(str, ";", ""); str = stringutils.replace(str, "\\", ""); //转换成小写 str = str.tolowercase(); //非法字符 string[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"}; //判断是否包含非法字符 for(string keyword : keywords){ if(str.indexof(keyword) != -1){ throw new rrexception("包含非法字符"); } } return str; } }