文章目录
项目源码地址 https://github.com/nieandsun/security
1.上篇文章存在的问题
上篇文章虽然完成了自定义登陆页面的目标,但是却存在一些问题:
不符合restful风格
按照restful风格,我的请求localhost:8080/hello是一个rest请求,应该获得一个包含状态的json数据,但是在未登陆时,返回的确是一个html
不适用于前后端分离的项目
比如所我们现在的项目,前端是VUE,登陆页面就写在了前端项目里,前后端交互的方式都是ajax请求,前端获取的数据格式也全是带有状态码的json数据
本篇文章将着力去解决上面提到的问题.
2.配置认证行为
代码如下:
package com.nrsc.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/authentication/require")
.loginProcessingUrl("/nrsc/signIn")
.and()
.authorizeRequests()
.antMatchers("/authentication/require", "/nrsc-login.html")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
}
简单介绍:
3.在loginPage->即/authentication/require接口中解决上面提到的问题
注意:
- ResultVO,ResultEnum和ResultVOUtil是我定义的三个用于封装返回结果的类,这里不再过多赘述,大家可以在文首提到的项目源码中获得.
- 下面的类初步解决了上面提到的两个问题------测试时会进一步去讲
package com.nrsc.security.controller;
import com.nrsc.security.VO.ResultVO;
import com.nrsc.security.enums.ResultEnum;
import com.nrsc.security.utils.ResultVOUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created By: Sun Chuan
* Created Date: 2019/6/15 19:55
*/
@RestController
@Slf4j
public class SecurityController {
//用户在访问我们的项目时,如果需要身份认证,spring-security会根据
//我在SecurityConfig中loginPage的配置跳转到我自定义的url即/authentication/require,
//但在这个跳转之前spring-security会将我们的请求缓存到RequestCache的session里,
//通过该类可以从session中再将缓存的请求信息拿出来
private RequestCache requestCache = new HttpSessionRequestCache();
//spring-security提供的类,可以方便的进行重定向
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@RequestMapping("/authentication/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public ResultVO requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
//获取引发跳转的请求
SavedRequest savedRequest = requestCache.getRequest(request, response);
//如果有这个引发跳转的请求的话,且该请求以.html结尾,将重定向到我们的登陆页/nrsc-login.html
if (savedRequest != null) {
//获取请求的url
String targetUrl = savedRequest.getRedirectUrl();
log.info("引发跳转的请求是:" + targetUrl);
//如果请求url以.html结尾跳转到我们自己写的登录页----在前后端分离的项目里一般不会这样做
if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
redirectStrategy.sendRedirect(request, response, "/nrsc-login.html");
}
}
//如果有引发跳转的请求且不以html结尾
//或者如果没有引发跳转的请求----即直接访问authentication/require
//返回一个未认证的状态码并引导用户进行登陆
return ResultVOUtil.error(ResultEnum.UNAUTHORIZED.getCode(), ResultEnum.UNAUTHORIZED.getMessage());
}
}
4.测试
4.1 发送一个结尾为.html的请求,比如localhost:8080/aa.html
点击登陆
4.2 发送rest请求localhost:8080/hello
5.前后端分离项目用户登陆流程
通过上面的测试我们可以看到,当用户发送rest请求进行认证时,得到的将不再是html而是由我自定义的json数据,对于前后端分离的项目而言,当一个ajax请求得到的json数据中的code为我自定义的800时,就应该引导用户重新进入登陆页进行登陆—>而登陆的URL就是我们上面配置的"/nrsc/signIn", 为此我们可以做如下测试:
5.1先用postman请求localhost:8080/hello
5.2获得800的code后,再用postman请求localhost:8080/nrsc/signIn即模拟登陆功能
5.3点击send,完成用户信息认证—>并重新跳转回认证之前的请求
由4和5可以看到我们已经解决了文章开始时提到的问题.