赞
踩
SpringSecurity使用地是随机盐值加密
随机盐是在对密码摘要之前随机生成一个盐,并且会把这个盐的明文和摘要拼接一起保存
举个例子:密码是pwd,随机盐是abc,pwd+abc摘要后的信息是xyz,最后保存的密码就是abcxyz
随机盐 同一个密码,每次摘要后的结果都不同,但是可以根据摘要里保存的盐来校验摘要和明文密码是否匹配
在hashpw函数中, 我们可以看到以下这句
real_salt = salt.substring(off + 3, off + 25);
说明我们真正用于盐值加密的是real_salt, 从而保证了我们生成随机盐值也能在校验时通过相同的规则得到需要的结果
/密码使用盐值加密 BCryptPasswordEncoder//BCrypt.hashpw() ==> 加密//BCrypt.checkpw() ==> 密码比较//我们在数据库中存储的都是加密后的密码, 只有在网页上输入时是明文的@Beanpublic PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder();}`
`@Overridepublic Integer addUser(UserDTO user) { //先查看要添加的用户是否在数据库中 String username = user.getUsername(); UserDTO userByUsername = getUserByUsername(username); //如果待插入的用户存在在数据库中, 插入0条 if (null != userByUsername) { return 0; } else { //不存在, 则插入用户 //先对密码进行盐值加密, SpringSecurity中使用的是随机盐值加密 String hashpw = passwordEncoder.encode(user.getPassword()); user.setPassword(hashpw); return userMapper.addUser(user); }}`
可以看到, 密码与我们明文输入的 123456 完全不同
讲在前面的话:
认证的配置类的 setFilterProcessesUrl("/login") (这里是自定义过滤器的配置, form方式与其一致)中, url只是我们提交表单或者ajax请求的地址, 不需要在Controller中注册, 注册了PostMapping也不会走, 但是会走Get方式, 此时SpringSecurity不会帮我们认证(认为是不安全的提交方式)
页面成功跳转有两个方法
前者是重定向, 后者是转发, 由于转发地址栏不会变化, 而我们SpringSecurity要求提交表单的方法必须为post(此处也是大坑!切记!), 因此请求类型后者依然为post
此时, 如果我们在addViewControllers中配置了首页的路径映射, 同时我们成功后要跳转到首页, 使用后一种方法就会报405错误, 提示我们请求类型错误
有两种解决方法
验证码校验我在之前的文章中提到过, 这里就不再赘述
主要说说验证码随认证一起提交的坑
设置提交的url和我们login的form url一致, 注意此时一定要用GET请求提交表单!
如果我们使用相同的url在controller层试图进行校验并重定向跳转, 可以发现根本就不会走我们的controller!
同时, 我们试图用拦截器拦截响应的url, 并在表单提交之前拦截来下进行校验, 也失败了
说明SpringSecurity自己的校验的优先级相当的高
此时, 我们只能实现一个认证成功的处理器来处理我们的验证码
`package com.wang.spring_security_framework.config.SpringSecurityConfig;import com.wang.spring_security_framework.service.CaptchaService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.Authentication;import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;//登录成功处理, 用于比对验证码@Componentpublic class LoginSuccessHandler implements AuthenticationSuccessHandler { @Autowired CaptchaService captchaService; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //校验验证码 Boolean verifyResult = captchaService.versifyCaptcha(request.getParameter("token"), request.getParameter("inputCode")); if (verifyResult) { response.sendRedirect("/index"); } else { response.sendRedirect("/toLoginPage"); } }}`
`@Overrideprotected void configure(HttpSecurity http) throws Exception { //指定自定义的登录页面, 表单提交的url, 以及成功后的处理器 http.formLogin() .usernameParameter("username") .passwordParameter("password") .loginPage("/toLoginPage") .loginProcessingUrl("/login") .successHandler(loginSuccessHandler) .and() .csrf() .disable();}`
此处有个大坑, 如果设置了成功的处理类, 我们就千万不要在配置类中写成功跳转的方法了, 这样会覆盖掉我们的成功处理器!
此处为天坑! 足足费了我快一天半才爬出来! 简直到处都是坑, 还有一个问题没解决...
总之不推荐这么干, 主要指用AJAX请求再用后台跳转
好了, 让我们来看看这个坑吧!
` 登录界面
登录
`
* 这里主要是$.ajaxSetup()方法, 可以定义全局的(同一个函数中的)ajax的一些参数, 尤其是里面的complete方法, 是在全部执行完之后调用的, 为了能强行跳转AJAX, 我们要天剑请求头, 我们在后面的后端代码中可以看到* 我们还需要写$.ajax()传递数据, 注意, json数据就算我们用json的格式写了, 还是要用JSON.stringify()方法转一下, 否则传到后端的不是JSON!* 此处有一个没有解决的问题, 不知道为什么不会走成功的回调函数, 只会走失败的回调函数
`package com.wang.spring_security_framework.config.SpringSecurityConfig; import com.alibaba.fastjson.JSON; import org.springframework.http.MediaType; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.util.Map; //默认的提取用户名和密码是通过 request.getParameter() 方法来提取的, 所以通过form我们可以提取到 //但是如果我们用ajax传递的话, 就提取不到了, 需要自己写过滤器! //这里不能写 @Component, 因为我们要在SpringSecurity配置类中注册 myCustomAuthenticationFilter 并配置 //否则会爆出重名的Bean! public class MyCustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //如果request请求是一个json同时编码方式为UTF-8 if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) { UsernamePasswordAuthenticationToken authRequest = null; Map authenticationBean = null; try (InputStream inputStream = request.getInputStream()) { //将JSON转为map authenticationBean = JSON.parseObject(inputStream, Map.class); //将用户名和密码放入 authRequest authRequest = new UsernamePasswordAuthenticationToken( authenticationBean.get("username"), authenticationBean.get("password")); System.out.println(authenticationBean); } catch (IOException e) { e.printStackTrace(); //出现IO异常, 放空的用户信息 authRequest = new UsernamePasswordAuthenticationToken("", ""); } finally { //将请求 request 和解析后的用户信息 authRequest 放入userDetails中 setDetails(request, authRequest); //将我们前端传递的JSON对象继续放在request里传递, 这样我们就可以在认证成功的处理器中拿到它了! request.setAttribute("authInfo", authenticationBean); return this.getAuthenticationManager().authenticate(authRequest); } } else { return super.attemptAuthentication(request, response); } } }`
* 这里还是要强调一点, @Component会自动注册内部的全部的方法, 如果我们在别的地方@Bean了方法, 会报一些奇怪的错误, 本质上是冲突了!* 此处我们是用FastJSON将JSON转为了Map
`package com.wang.spring_security_framework.config.SpringSecurityConfig; import com.alibaba.fastjson.JSON; import com.wang.spring_security_framework.service.CaptchaService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; //登录成功处理 //我们不能在这里获得request了, 因为我们已经在前面自定义了认证过滤器, 做完后SpringSecurity会关闭inputStream流 @Component public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Autowired CaptchaService captchaService; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //我们从自定义的认证过滤器中拿到的authInfo, 接下来做验证码校验和跳转 Map authInfo = (Map) request.getAttribute("authInfo"); System.out.println(authInfo); System.out.println("success!"); String token = authInfo.get("token"); String inputCode = authInfo.get("inputCode"); //校验验证码 Boolean verifyResult = captchaService.versifyCaptcha(token, inputCode); System.out.println(verifyResult); if (verifyResult) { HashMap map = new HashMap<>(); map.put("url", "/index"); System.out.println(map); String VerifySuccessUrl = "/index"; response.setHeader("Content-Type", "application/json;charset=utf-8"); // response.setContentType("application/json;charset=utf-8"); response.addHeader("REDIRECT", "REDIRECT"); response.addHeader("CONTEXTPATH", VerifySuccessUrl); } else { String VerifyFailedUrl = "/toRegisterPage"; response.setHeader("Content-Type", "application/json;charset=utf-8"); // response.setContentType("application/json;charset=utf-8"); response.addHeader("REDIRECT", "REDIRECT"); response.addHeader("CONTEXTPATH", VerifyFailedUrl); // response.sendRedirect("/toRegisterPage"); } } }`
* 这里需要注意一点, 我们需要从前面的Request拿到对象* addHeader里面我们为了重定向, 添加了响应头, 可以和前端的ajaxSetup对应着看
`package com.wang.spring_security_framework.config; import com.wang.spring_security_framework.config.SpringSecurityConfig.LoginSuccessHandler; import com.wang.spring_security_framework.config.SpringSecurityConfig.MyCustomAuthenticationFilter; import com.wang.spring_security_framework.service.UserService; import com.wang.spring_security_framework.service.serviceImpl.UserDetailServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; //SpringSecurity设置 @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService; @Autowired UserDetailServiceImpl userDetailServiceImpl; @Autowired LoginSuccessHandler loginSuccessHandler; //授权 @Override protected void configure(HttpSecurity http) throws Exception { //指定自定义的登录页面, 表单提交的url, 以及成功后的处理器 http.formLogin() .loginPage("/toLoginPage") .failureForwardUrl("/index") .and() .csrf() .disable(); // .failureForwardUrl(); //注销 //设置过滤器链, 添加自定义过滤器 http.addFilterAt( myCustomAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class ); //允许iframe // http.headers().frameOptions().sameOrigin(); } //注册自定义过滤器 @Bean MyCustomAuthenticationFilter myCustomAuthenticationFilter() throws Exception { MyCustomAuthenticationFilter filter = new MyCustomAuthenticationFilter(); //设置过滤器认证管理 filter.setAuthenticationManager(super.authenticationManagerBean()); //设置filter的url filter.setFilterProcessesUrl("/login"); //设置登录成功处理器 filter.setAuthenticationSuccessHandler(loginSuccessHandler); //TODO 设置登录失败处理器 return filter; } //密码使用盐值加密 BCryptPasswordEncoder //BCrypt.hashpw() ==> 加密 //BCrypt.checkpw() ==> 密码比较 //我们在数据库中存储的都是加密后的密码, 只有在网页上输入时是明文的 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }`
* 这里主要干了两件事 * 注册了我们自定义的过滤器 * 在过滤器链中注册我们的过滤器
这里主要修改了两处, 我们的成功处理器返回的是一个封装好的JSON, 同时我们在ajax的回调函数中写了页面跳转的逻辑
`package com.wang.spring_security_framework.config.SpringSecurityConfig; import com.alibaba.fastjson.JSON; import com.wang.spring_security_framework.service.CaptchaService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; //登录成功处理 //我们不能在这里获得request了, 因为我们已经在前面自定义了认证过滤器, 做完后SpringSecurity会关闭inputStream流 @Component public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Autowired CaptchaService captchaService; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //我们从自定义的认证过滤器中拿到的authInfo, 接下来做验证码校验和跳转 Map authInfo = (Map) request.getAttribute("authInfo"); System.out.println(authInfo); System.out.println("success!"); String token = authInfo.get("token"); String inputCode = authInfo.get("inputCode"); //校验验证码 Boolean verifyResult = captchaService.versifyCaptcha(token, inputCode); System.out.println(verifyResult); Map result = new HashMap<>(); if (verifyResult) { HashMap map = new HashMap<>(); map.put("url", "/index"); System.out.println(map); String VerifySuccessUrl = "/index"; response.setHeader("Content-Type", "application/json;charset=utf-8"); result.put("code", "200"); result.put("msg", "认证成功!"); result.put("url", VerifySuccessUrl); PrintWriter writer = response.getWriter(); writer.write(JSON.toJSONString(result)); } else { String VerifyFailedUrl = "/toLoginPage"; response.setHeader("Content-Type", "application/json;charset=utf-8"); result.put("code", "201"); result.put("msg", "验证码输入错误!"); result.put("url", VerifyFailedUrl); PrintWriter writer = response.getWriter(); writer.write(JSON.toJSONString(result)); } } }`
* 这里只需要注意一点, 及时ContentType一定要加上, 防止出现奇怪的响应头的问题
`$.ajax({ data: JSON.stringify(JsonData), success: function (data) { alert("进入success---"); let code = data.code; let url = data.url; let msg = data.msg; if (code == 200) { alert(msg); window.location.href = url; } else if (code == 201) { alert(msg); window.location.href = url; } else { alert("未知错误!") } }, error: function (xhr, textStatus, errorThrown) { alert("进入error---"); alert("状态码:" + xhr.status); alert("状态:" + xhr.readyState); //当前状态,0-未初始化,1-正在载入,2-已经载入,3-数据进行交互,4-完成。 alert("错误信息:" + xhr.statusText); alert("返回响应信息:" + xhr.responseText);//这里是详细的信息 alert("请求状态:" + textStatus); alert(errorThrown); alert("请求失败"); } });`
认证失败的处理器, 主要是三个部分, 失败处理器, 配置类中自定义过滤器添加失败处理器, 以及前端添加回调函数的失败处理器的跳转逻辑
其中配置类和前端都非常简单, 我们这里只贴出失败处理器供大家参考
`package com.wang.spring_security_framework.config.SpringSecurityConfig;import com.alibaba.fastjson.JSON;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.util.HashMap;//认证失败的处理器@Componentpublic class LoginFailHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { HashMap result = new HashMap<>(); String AuthenticationFailUrl = "/toRegisterPage"; response.setHeader("Content-Type", "application/json;charset=utf-8"); result.put("code", "202"); result.put("msg", "认证失败!密码或用户名错误!即将跳转到注册页面!"); result.put("url", AuthenticationFailUrl); PrintWriter writer = response.getWriter(); writer.write(JSON.toJSONString(result)); }}`
如果觉得本文对你有帮助,可以点赞关注支持一下
作者: 山人西来
出处:https://www.cnblogs.com/wang-sky/p/14011660.html
如果觉得本文对你有帮助,可以点赞关注支持一下
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。