Java前后端分离第三方登录_前后端分离中,使用JSON格式登录原来这么简单!

【官方正版】图解算法 java清华出版社

55.2元

包邮

(需用券)

去购买 >

d179abdf753943b80c85ec5bd59cd325.png

做微人事的小伙伴(https://github.com/lenve/vhr),应该都发现了在微人事中有一个极为特殊的请求,那就是登录。

登录请求是一个 POST 请求,但是数据传输格式是 key/value 的形式。整个项目里就只有这一个 POST 请求是这样,其他 POST 请求都是 JSON 格式的数据。

为什么做成这个样子呢?还是懒呗。

因为 Spring Security 中默认的登录数据格式就是 key/value 的形式,一直以来懒得改。最近刚好在录 Spring Security,就抽空把这里调整了下,这样前后端就能统一起来了。

好了,我们一起来看下怎么实现。

1.服务端接口调整

首先大家知道,用户登录的用户名/密码是在 UsernamePasswordAuthenticationFilter 类中处理的,具体的处理代码如下:

public Authentication attemptAuthentication(HttpServletRequest request,

HttpServletResponse response) throws AuthenticationException {

String username = obtainUsername(request);

String password = obtainPassword(request);

//省略

}

protected String obtainPassword(HttpServletRequest request) {

return request.getParameter(passwordParameter);

}

protected String obtainUsername(HttpServletRequest request) {

return request.getParameter(usernameParameter);

}

从这段代码中,我们就可以看出来为什么 Spring Security 默认是通过 key/value 的形式来传递登录参数,因为它处理的方式就是 request.getParameter。

所以我们要定义成 JSON 的,思路很简单,就是自定义来定义一个过滤器代替 UsernamePasswordAuthenticationFilter ,然后在获取参数的时候,换一种方式就行了。

这里有一个额外的点需要注意,就是我们的微人事现在还有验证码的功能,所以如果自定义过滤器,要连同验证码一起处理掉。

2.自定义过滤器

接下来我们来自定义一个过滤器代替 UsernamePasswordAuthenticationFilter ,如下:

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

@Override

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

if (!request.getMethod().equals("POST")) {

throw new AuthenticationServiceException(

"Authentication method not supported: " + request.getMethod());

}

String verify_code = (String) request.getSession().getAttribute("verify_code");

if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)) {

Map loginData = new HashMap<>();

try {

loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class);

} catch (IOException e) {

}finally {

String code = loginData.get("code");

checkCode(response, code, verify_code);

}

String username = loginData.get(getUsernameParameter());

String password = loginData.get(getPasswordParameter());

if (username == null) {

username = "";

}

if (password == null) {

password = "";

}

username = username.trim();

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(

username, password);

setDetails(request, authRequest);

return this.getAuthenticationManager().authenticate(authRequest);

} else {

checkCode(response, request.getParameter("code"), verify_code);

return super.attemptAuthentication(request, response);

}

}

public void checkCode(HttpServletResponse resp, String code, String verify_code) {

if (code == null || verify_code == null || "".equals(code) || !verify_code.toLowerCase().equals(code.toLowerCase())) {

//验证码不正确

throw new AuthenticationServiceException("验证码不正确");

}

}

}

这段逻辑我们基本上是模仿官方提供的 UsernamePasswordAuthenticationFilter 来写的,我来给大家稍微解释下:

首先登录请求肯定是 POST,如果不是 POST ,直接抛出异常,后面的也不处理了。

因为要在这里处理验证码,所以第二步从 session 中把已经下发过的验证码的值拿出来。

接下来通过 contentType 来判断当前请求是否通过 JSON 来传递参数,如果是通过 JSON 传递参数,则按照 JSON 的方式解析,如果不是,则调用 super.attemptAuthentication 方法,进入父类的处理逻辑中,也就是说,我们自定义的这个类,既支持 JSON 形式传递参数,也支持 key/value 形式传递参数。

如果是 JSON 形式的数据,我们就通过读取 request 中的 I/O 流,将 JSON 映射到一个 Map 上。

从 Map 中取出 code,先去判断验证码是否正确,如果验证码有错,则直接抛出异常。验证码的判断逻辑,大家可以参考:松哥手把手教你给微人事添加登录验证码。

接下来从 Map 中取出 username 和 password,构造 UsernamePasswordAuthenticationToken 对象并作校验。

过滤器定义完成后,接下来用我们自定义的过滤器代替默认的 UsernamePasswordAuthenticationFilter,首先我们需要提供一个 LoginFilter 的实例:

@Bean

LoginFilter loginFilter() throws Exception {

LoginFilter loginFilter = new LoginFilter();

loginFilter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {

@Override

public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

response.setContentType("application/json;charset=utf-8");

PrintWriter out = response.getWriter();

Hr hr = (Hr) authentication.getPrincipal();

hr.setPassword(null);

RespBean ok = RespBean.ok("登录成功!", hr);

String s = new ObjectMapper().writeValueAsString(ok);

out.write(s);

out.flush();

out.close();

}

});

loginFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {

@Override

public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

response.setContentType("application/json;charset=utf-8");

PrintWriter out = response.getWriter();

RespBean respBean = RespBean.error(exception.getMessage());

if (exception instanceof LockedException) {

respBean.setMsg("账户被锁定,请联系管理员!");

} else if (exception instanceof CredentialsExpiredException) {

respBean.setMsg("密码过期,请联系管理员!");

} else if (exception instanceof AccountExpiredException) {

respBean.setMsg("账户过期,请联系管理员!");

} else if (exception instanceof DisabledException) {

respBean.setMsg("账户被禁用,请联系管理员!");

} else if (exception instanceof BadCredentialsException) {

respBean.setMsg("用户名或者密码输入错误,请重新输入!");

}

out.write(new ObjectMapper().writeValueAsString(respBean));

out.flush();

out.close();

}

});

loginFilter.setAuthenticationManager(authenticationManagerBean());

loginFilter.setFilterProcessesUrl("/doLogin");

return loginFilter;

}

当我们代替了 UsernamePasswordAuthenticationFilter 之后,原本在 SecurityConfig#configure 方法中关于 form 表单的配置就会失效,那些失效的属性,都可以在配置 LoginFilter 实例的时候配置。

另外记得配置一个 AuthenticationManager,根据 WebSecurityConfigurerAdapter 中提供的配置即可。

FilterProcessUrl 则可以根据实际情况配置,如果不配置,默认的就是 /login。

最后,我们用自定义的 LoginFilter 实例代替 UsernamePasswordAuthenticationFilter,如下:

@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

...

//省略

http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);

}

调用 addFilterAt 方法完成替换操作。

篇幅原因,我这里只展示了部分代码,完整代码小伙伴们可以在 GitHub 上看到:https://github.com/lenve/vhr。

配置完成后,重启后端,先用 POSTMAN 测试登录接口,如下:

49e309deab7bb05b6a753bacc9a498c5.png

3.前端修改

原本我们的前端登录代码是这样的:

this.$refs.loginForm.validate((valid) => {

if (valid) {

this.loading = true;

this.postKeyValueRequest('/doLogin', this.loginForm).then(resp => {

this.loading = false;

//省略

})

} else {

return false;

}

});

首先我们去校验数据,在校验成功之后,通过 postKeyValueRequest 方法来发送登录请求,这个方法是我自己封装的通过 key/value 形式传递参数的 POST 请求,如下:

export const postKeyValueRequest = (url, params) => {

return axios({

method: 'post',

url: `${base}${url}`,

data: params,

transformRequest: [function (data) {

let ret = '';

for (let i in data) {

ret += encodeURIComponent(i) + '=' + encodeURIComponent(data[i]) + '&'

}

return ret;

}],

headers: {

'Content-Type': 'application/x-www-form-urlencoded'

}

});

}

export const postRequest = (url, params) => {

return axios({

method: 'post',

url: `${base}${url}`,

data: params

})

}

postKeyValueRequest 是我封装的通过 key/value 形式传递参数,postRequest 则是通过 JSON 形式传递参数。

所以,前端我们只需要对登录请求稍作调整,如下:

this.$refs.loginForm.validate((valid) => {

if (valid) {

this.loading = true;

this.postRequest('/doLogin', this.loginForm).then(resp => {

this.loading = false;

//省略

})

} else {

return false;

}

});

配置完成后,再去登录,浏览器按 F12 ,就可以看到登录请求的参数形式了:

1367e74b68da313674fb1bbe3b2f40cc.png

好啦,这就是松哥和大家介绍的 SpringSecurity+JSON+验证码登录,如果觉得还不错,记得点一下右下角在看哦。

完整代码小伙伴们可以在 GitHub 上下载:https://github.com/lenve/vhr

java 11官方入门(第8版)教材

79.84元

包邮

(需用券)

去购买 >

f0f3f55624fb396b1764d42d6df88864.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值