Springboot之登录模块探索(含Token,验证码,网络安全等知识)

前端开发 作者: 2024-08-22 13:20:01
简介 登录模块很简单,前端发送账号密码的表单,后端接收验证后即可~ 淦!可是我想多了,于是有了以下几个问题(里面还包含网络安全问题): 1.登录时的验证码 2.自动登录的实现 3.怎么维护前后端登录状

简介

1.登录时的验证码

2.自动登录的实现

3.如何维护前后端登录状态

什么是CSRF攻击

这说明这个请求是从 http://localhost:8099/swr 中发出的

浏览器跨域访问会发生什么

  1. DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
  2. XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。(就是ajax)
  1 /**
  2  * 访问后台的对象,为ajax封装
  3  * @param url 后台资源路径
  4  * @param param Map参数
  5  * @param contentType 传输类型
  6  * @param success   成功回调函数
  7  * @param error 失败回调函数
  8  * @param requestType 请求类型(get.post,put,delete)
  9  * @constructor
 10  */
 11 var Query = function (url,param,contentType,successFunc,errorFunc,requestType) {
 12     this.url = url;
 13 
 14     //先确认参数存在
 15     if (param) {
 16         如果是get请求类型,则将参数拼接到url后面
 17         if (requestType == Query.GET_TYPE) {
 18             this.param = this._concatParamToURL(param,url);
 19         } else {
 20             其他请求类型,要根据不同的传输格式来确定传输的值的类型
 21             if (contentType == Query.NOMAL_TYPE) {
 22                 this.param = JSON.parse(._convertParamToJson(param));
 23             }  24                 ._convertParamToJson(param);
 25             }
 26         }
 27     }  28         null;
 29     }
 30 
 31 
 32     this.contentType = contentType;
 33     this.successFunc = successFunc;
 34     this.errorFunc = errorFunc;
 35     请求超时,默认10秒
 36     this.timeout = 10000 37     是否异步请求,默认异步
 38     this.async = true 39     this.requestType = requestType;
 40 }
 41 
 42 Query.JSON_TYPE = 'application/json' 43 Query.NOMAL_TYPE = 'application/x-www-form-urlencoded' 44 
 45  46  * ajax请求的访问
 47  * 默认是post
 48  * @param url 要访问的地址
 49  * @param paramMap 传给后台的Map参数,key为字符串类型
 50  * @param callback 回调函数
 51  * @param contentType 传输数据的格式  默认传输application/x-www-form-urlencoded格式
 52   53 Query.create =  54     return new Query(url,Query.NOMAL_TYPE,Query.GET_TYPE);
 55  56 
 57 -----------------------以下为RESTFul方法---------------------------
 58 ajax请求类型
 59 Query.GET_TYPE = "get" 60 Query.POST_TYPE = "post" 61 Query.PUT_TYPE = "put" 62 Query.DELETE_TYPE = "delete" 63 
 64 get方法默认是Query.NOMAL_TYPE
 65 Query.createGetType =  66      67  68 Query.createPostType =  69      70  71 Query.createPutType =  72      73  74 Query.createDeleteType =  75      76  77 
 78  79  * 将paramMap参数转为json格式
 80  * @param paramMap
 81  * @private
 82   83 Query.prototype._convertParamToJson =  (paramMap) {
 84 
 85     return window.tool.strMap2Json(paramMap);
 86 
 87  88 
 89  90  * 将参数拼接至URL尾部
 91  92  * @param url
 93  94   95 Query.prototype._concatParamToURL =  (paramMap,url) {
 96     let size = paramMap.size;
 97 
 98     if (size > 0) {
 99         let count = 0100         url = url + "?"101         let urlParam = ""102 
103         for (let [k,v] of paramMap) {
104             urlParam = urlParam + encodeURIComponent(k) + "=" + encodeURIComponent(v);
105             if (count < size-1106                 urlParam = urlParam + " && "107                 count++108 109 110         url = url + urlParam;
111 112     113 114 
115 ajax需要跳转的界面
116 Query.REDIRECT_URL = "REDIRECT_URL"117 
118 119  * ajax成功返回时调用的方法
120  * 会根据ajax的ContentType类型,转换Response对象的data给回调的成功函数
121  * 如application/json格式类型,data会转成json类型传递
122  * @param queryResult 返回的值,通常为后台的Response对象
123 124  125 Query.prototype._successFunc =  (queryResult) {
126     var data = .__afterSuccessComplete(queryResult);
127     if (.successFunc) {
128         .successFunc(data);
129 130 
131     如果有需要跳转的页面,则自动跳转
132     if (data && data.REDIRECT_URL != 133         window.location = data.REDIRECT_URL;
134 135 136 
137 138  * 会根据ajax的ContentType类型,转换Response对象的data给回调的失败函数
139 140  * 如果对获得的参数不满意,可以用this.getMsg或this.getJsonMsg来进行获取(this指Query对象)
141  *
142  * 这里错误分3种
143  * 1.是Web容器出错
144  * 2.是Filter过滤器主动报错(如一些校验失败后主动抛出,会有错误提示)
145  * 3.是Spring抛出,Spring异常会全局捕捉进行封装
146 147 148  149 Query.prototype._errorFunc = 150 
151     返回的信息
152     .__afterErrorComplete(queryResult);
153     如果data里面没东西
154     if (!data) {
155         data = queryResult.statusText;
156 157 
158     是否调用者自身已解决了错误
159     var handleError = false160 
161     调用回调函数,如果返回结果为true,则不会默认错误处理
162     this.errorFunc instanceof Function) {
163         handleError = .errorFunc(data);
164 165 
166     错误编号
167     var code;
168     错误信息
169      msg;
170 
171     没有取消对错误的后续处理,那么进行跳转
172     handleError) {
173 
174         如果data成功转为Json对象
175          (data) {
176             Filter过滤器主动报错(如一些校验失败后主动抛出,会有错误提示)
177              (data.status) {
178                 code = data.status;
179 180              (data.message) {
181                 msg = data.message;
182 183 184 
185         最终跳转至错误页面
186         var path = "/system/error"187         if (code && msg) {
188             path = path + "/" + error.code + "/" + error.msg;
189 190         window.location.href = path;
191 192 193 
194 Query.SUCCESS_TYPE = "SUCCESS_TYPE"195 Query.ERROR_TYPE = "ERROR_TYPE"196 197  * 当一个请求完成时,无论成功或失败,都要调用此函数做一些处理
198  * @param queryResult 服务端返回的数据
199  * @returns {*}
200 201  202 Query.prototype._afterComplete = 203     ._cancleLoadDom();
204 205 
206 207  * 成功的返回处理,会将data部分转为对象
208  * 默认application/json会进行单引号转双引号
209 210  * @param queryResult
211 212 213  214 Query.prototype.__afterSuccessComplete = 215     ._afterComplete();
216     this.response = queryResult;
217 
218     var data = queryResult.data;
219     data必须要有内容,且不是对象才有转换的意义
220     if (data && !(data  Object)) {
221             data = .getJsonMsg();
222 223      data;
224 225 
226 227  * 失败的返回处理
228  * 最终会根据ajax的contentType来进行data相应类型转换
229 230 231 232  233 Query.prototype.__afterErrorComplete = 234     235     236      queryResult.responseJSON;
237     238         data = queryResult.responseText;
239 240 
241     242 243 
244 245  * 取消请求时的等待框
246 247  248 Query.prototype._cancleLoadDom =  () {
249     取消加载框
250     .loadDom) {
251         $(this.loadDom).remove("#loadingDiv");
252 253 254 
255 256  * 正式发送ajax
257 258  259 Query.prototype.sendMessage = 260     var self = 261     var xhr = $.ajax(
262         {
263             url: .url,264             type: .requestType,1)">265             contentType: .contentType,1)">266             data: .param,1)">267              ajax发送前调用的方法,初始化等待动画
268              @param XHR  XMLHttpRequest对象
269             beforeSend:  (XHR) {
270                 试图从Cookie中获得token放入http头部
271                 var token = window.tool.getCookieMap().get(window.commonStaticValue.TOKEN);
272                 (token){
273                     XHR.setRequestHeader(window.commonStaticValue.TOKEN,token);
274                 }
275 
276                 绑定本次请求的queryObj
277                 XHR.queryObj = self;
278                 if (self.beforeSendFunc 279                     self.beforeSendFunc(XHR);
280 281 
282                 if (self.loadDom  HTMLElement) {
283                     self.loadDom.innerText = ""284                     $(self.loadDom).append("<div id='loadingDiv' class='loading'><img src='/image/loading.gif'/></div>"285                 } else  jQuery) {
286                     self.loadDom.empty();
287                     self.loadDom.append("<div id='loadingDiv' class='loading'><img src='/image/loading.gif'/></div>"288 289             },1)">290             将QueryObj设置为上下文
291             context: self,1)">292             success: ._successFunc,1)">293             error: ._errorFunc,1)">294             complete:(){
295               console.log("ajax完成"296 297             timeout: .timeout,1)">298             async: .async
299 300     );
301 302 
303 -----------------------------------下面提供了获取后台返回信息方法(帮忙封装了)
304 305  * 获取返回信息Response的Meta头
306  307 Query.prototype.getMeta = 308     .response.meta;
309 310 
311 312  * 获得返回值里的data部分
313 314  315 Query.prototype.getMsg = 316     .response.data;
317 318 
319 320  * 获得返回值里的data部分,尝试将其转为Json对象
321  322 Query.prototype.getJsonMsg = 323     324     325             先将字符串里的&quot;转为双引号
326             var data = window.tool.replaceAll(data,"&quot;","\""327             try{
328                 var jsonData = JSON.parse(data);
329                  jsonData;
330             }catch (e) {
331                 332 333 334 335 
336 ------------------------以下为对Query的参数设置---------------------------
337 338  * 在ajax发送前设置参数,可以有加载的动画,并且请求完成后会自动取消
339  * @param loadDom 需要显示动画的dom节点
340  * @param beforeSendFunc ajax发送前的自定义函数
341  342 Query.prototype.setBeforeSend =  (loadDom,beforeSendFunc) {
343     this.loadDom = loadDom;
344     this.beforeSendFunc = beforeSendFunc;
345 346 
347 348  * 设置超时时间
349  * @param timeout
350  351 Query.prototype.setTimeOut =  (timeout) {
352     this.timeout = timeout;
353 354 
355 Query.prototype.setAsync =  (async) {
356     this.async = async;
357 }
View Code

Xss攻击是什么

<script>alert(123)</>
<!DOCTYPE html>
html>
    head>
        meta charset="utf-8" />
        title></p><body>
 1 /**
 2  * @auther: NiceBin
 3  * @description: 系统的拦截器,注册在FilterConfig类中进行
 4  *               不能使用@WebFilter,因为Filter要排序
 5  *               1.对ServletRequest进行封装
 6  *               2.防止CSRF,检查http头的Referer字段
 7  * @date: 2020/12/15 15:32
 8   9 @Component
10 public class SystemFilter implements Filter {
11     private final Logger logger = LoggerFactory.getLogger(SystemFilter.class12     @Autowired
13     private Environment environment;
14 
15     @Override
16     void init(FilterConfig filterConfig) throws ServletException {
17         logger.info("系统拦截器SystemFilter开始加载"18 19 
20 21     void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)  IOException,ServletException {
22         SystemHttpServletRequestWrapper requestWrapper =  SystemHttpServletRequestWrapper((HttpServletRequest) request);
23 
24         检测http的Referer字段,不允许跨域访问
25         String hostPath = environment.getProperty("server.host-path"26         String referer = requestWrapper.getHeader("Referer"27         if(!Tool.isNull(referer)){
28             if(referer.lastIndexOf(hostPath)!=0){
29                 ((HttpServletResponse)response).setStatus(HttpStatus.FORBIDDEN.value()); //设置错误状态码
30                 31 32 33         chain.doFilter(requestWrapper,response);
34 35 
36 37     void destroy() {
38 
39 40 }
 * @description: 包装的httpServlet,进行以下增强
 *               1.将流数据取出保存,方便多次读出
 *               2.防止XSS攻击,修改读取数据的方法,过滤敏感字符
 * @date: 2020/4/23 19:50
 7   8 class SystemHttpServletRequestWrapper extends HttpServletRequestWrapper {
 9     final byte[] body;
10      HttpServletRequest request;
11 
12     public SystemHttpServletRequestWrapper(HttpServletRequest request)  IOException {
13         super(request);
14         打印属性
15         printRequestAll(request);
16         body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));  //HttpHelper是我自己写的工具类
17         this.request = request;
public BufferedReader getReader() 22         new BufferedReader( InputStreamReader(getInputStream()));
23 24 
25 26     public ServletInputStream getInputStream() final ByteArrayInputStream bais =  ByteArrayInputStream(body);
28          ServletInputStream() {
            @Override
30             boolean isFinished() {
31                 33 
35              isReady() {
36                 37 40              setReadListener(ReadListener readListener) {
41 
42 43 
44 45             int read() 46                  bais.read();
47 48         };
49 50 
51     52      * 可以打印出HttpServletRequest里属性的值
53      * @param request
54      55      printRequestAll(HttpServletRequest request){
56         Enumeration e = request.getHeaderNames();
57         while (e.hasMoreElements()) {
58             String name = (String) e.nextElement();
59             String value = request.getHeader(name);
60             System.out.println(name + " = " + value);
61 62 63 
64     以下为XSS预防
65 66     public String getParameter(String name) {
67         String value = request.getParameter(name);
68         StringUtils.isEmpty(value)) {
69             value = StringEscapeUtils.escapeHtml4(value);
70 71          value;
72 73 
74 75      String[] getParameterValues(String name) {
76         String[] parameterValues = .getParameterValues(name);
77         if (parameterValues == 78             79 80         for (int i = 0; i < parameterValues.length; i++81             String value = parameterValues[i];
82             parameterValues[i] =83 84          parameterValues;
85 86 }
 HttpHelper {
 2          * 获取请求中的Body内容
@return
 6       7     static String getBodyString(ServletRequest request) {
 8         StringBuilder sb =  StringBuilder();
 9         InputStream inputStream = 10         BufferedReader reader = 11         12             inputStream = request.getInputStream();
13             reader = new InputStreamReader(inputStream,Charset.forName("UTF-8")));
14             String line = ""15             while ((line = reader.readLine()) != 16                 sb.append(line);
17 18         }  (IOException e) {
19             e.printStackTrace();
20         } finally21             if (inputStream != 22                                     inputStream.close();
24                 }                     e.printStackTrace();
26 27 if (reader != 29                 30                     reader.close();
31                 } 35 36          sb.toString();
38 }
View Code
inputStream = request.getInputStream();
reader = 
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
 * @description: 为了排序Filter,如果Filter有顺序要求
 *               那么需要在此注册,设置order(值越低优先级越高)
 *               其他没顺序需要的,可以@WebFilter注册
 *               如@WebFilter(filterName = "SecurityFilter",urlPatterns = "/*",asyncSupported = true)
 * @date: 2020/12/15 15:48
@Configuration
 FilterConfig {
13     SystemFilter systemFilter;
14          * 注册SystemFilter,顺序为1,任何其他filter不能比他优先
17          @Bean
19      FilterRegistrationBean filterRegist(){
20         FilterRegistrationBean filterRegistrationBean =  FilterRegistrationBean();
21         filterRegistrationBean.setFilter(systemFilter);
22         filterRegistrationBean.setName("SystemFilter"23         filterRegistrationBean.addUrlPatterns("/*"24         filterRegistrationBean.setAsyncSupported(25         filterRegistrationBean.setOrder(126          filterRegistrationBean;
28 }

知识点提问:在我们之后的Filter或者Interceptor中,需要

1 SystemHttpServletRequestWrapper requestWrapper = (SystemHttpServletRequestWrapper) request
 Father {
     sayName(){
        System.out.println("我是爸爸");
    }
}

class Son  Father{
     sayName(){
        System.out.println("我是儿子" Test {

    @org.junit.Test
    void test()  Exception {
        Father father =  Son();
        otherMethod(father);
    }

     otherMethod(Father father){
        father.sayName();
    }
}

头部(header)

{
  "alg": "HS256","typ": "JWT"
}

载荷(Payload)

{
  "sub": "1"

}

签名(Signature)

HMACSHA256(

    base64UrlEncode(header) + "." +

    base64UrlEncode(payload),secret

)
<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.2</version>
        </dependency>
        <!--jwt一些工具类-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
</dependency>
  1 **
  2  * @auther: NiceBin
  3  * @description: Jwt构造器,创建Token来进行身份记录
  4  * jwt由3个部分构成:jwt头,有效载荷(主体,payLoad),签名
  5  * @date: 2020/5/7 22:40
  6  */
 JwtTool {
  8 
  9     以下为JwtTool生成时的主题
 10     登录是否还有效
 11     static final String SUBJECT_ONLINE_STATE = "online_state" 12 
 13     以下为载荷固定的Key值
主题
final String SUBJECT = "subject" 16     发布时间
 17     final String TIME_ISSUED = "timeIssued" 18     过期时间
 19     final String EXPIRATION = "expiration" 20 
 21      22      * 生成token,参数都是载荷(自定义内容)
 23      * 其中Map里为非必要数据,而其他参数为必要参数
 24      *
 subject  主题,token生成干啥用的,用上面的常量作为参数
 liveTime 存活时间(秒单位),建议使用TimeUnit方便转换
 27      *                 如TimeUnit.HOURS.toSeconds(1);将1小时转为秒 = 3600
 28  claimMap 自定义荷载,可以为空
 30       31     static String createToken(String subject,long liveTime,HashMap<String,String> claimMap)  Exception {
 32 
 33         SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
 34 
 35         毫秒要转为秒
 36         long now = System.currentTimeMillis() / 1000 37 
 38         byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(EncrypRSA.keyString);
 39 //
        Key signingKey = new SecretKeySpec(apiKeySecretBytes,signatureAlgorithm.getJcaName());
 42         JwtBuilder jwtBuilder = Jwts.builder()
 43                 加密算法
 44                 .setHeaderParam("alg","HS256")
 45                 jwt签名
                .signWith(signatureAlgorithm,EncrypRSA.convertSecretKey);  //这个Key是我自个的密码,你们自己设个字符串也成,这个得保密
 47 
 48         HashMap<String,String> payLoadMap = new HashMap<>();
        payLoadMap.put(SUBJECT,subject);
        payLoadMap.put(TIME_ISSUED,String.valueOf(now));
 51         设置Token的过期时间
 52         if (liveTime >= 0 53             long expiration = now + liveTime;
 54             payLoadMap.put(EXPIRATION,String.valueOf(expiration));
 55         }  56             throw new SystemException(SystemStaticValue.TOOL_PARAMETER_EXCEPTION_CODE,"liveTime参数异常" 58 
 59         StringBuilder payLoad =  60 
 61 
 62 
 63         Collections.isEmpty(claimMap)) {
            payLoadMap.putAll(claimMap);
 65  66 
 67         拼接主题payLoad,采用 key1,value1,key2,value2的格式
 68         for (Map.Entry<String,String> entry : payLoadMap.entrySet()) {
 69             payLoad.append(entry.getKey()).append(',').append(entry.getValue()).append(',' 71 
 72         对payLoad进行加密,这样别人Base64URL解密后也不是明文
 73         String encrypPayLoad = EncrypRSA.encrypt(payLoad.toString());
 74 
 75         jwtBuilder.setPayload(encrypPayLoad);
 76 
 77         会自己生成签名,组装
 78          jwtBuilder.compact();
 80 
 81      82      * 私钥解密token信息
 83  84  token
 85 @return 存有之前定义的Key,value的Map,解析失败则返回null
 86       87      HashMap getMap(String token) {
 88         Tool.isNull(token)) {
 89              90                 String encrypPayLoad = Jwts.parser()
                        .setSigningKey(EncrypRSA.convertSecretKey)
                        .parsePlaintextJws(token).getBody();
 93 
 94                 String payLoad = EncrypRSA.decrypt(encrypPayLoad);
 95 
 96                 String[] payLoads = payLoad.split("," 97                 HashMap<String,String> map =  98                 int i = 0; i < payLoads.length - 1; i=i+2 99                     map.put(payLoads[i],payLoads[i + 1]);
100 101                  map;
102             }  (Exception e) {
103                 System.out.println("Token解析失败"104                 105 106         } 107             110 
111     112      * 判断token是否有效
114  map 已经解析过token的map
 true 为有效
116      117     boolean isAlive(HashMap<String,1)"> map) {
118 
119         Collections.isEmpty(map)) {
120             String tokenString = map.get(EXPIRATION);
121 
122             Tool.isNull(tokenString)) {
123                 long expiration = Long.valueOf(tokenString) / 1000124                 long now = System.currentTimeMillis();
125                 if (expiration > now) {
126                     127                 } 128                     130 131 132         133 134 
135     136  token 还未被解析的token
139      140      isAlive(String token) {
141          JwtTool.isAlive(JwtTool.getMap(token));
143 }
原创声明
本站部分文章基于互联网的整理,我们会把真正“有用/优质”的文章整理提供给各位开发者。本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
本文链接:http://www.jiecseo.com/news/show_66685.html