乐趣区

SpringSecurity登录使用JSON格式数据

在使用 SpringSecurity 中,大伙都知道默认的登录数据是通过 key/value 的形式来传递的,默认情况下不支持 JSON 格式的登录数据,如果有这种需求,就需要自己来解决,本文主要和小伙伴来聊聊这个话题。
Java 通关秘笈小程序,视频教程、学习资料、重点知识一网打尽,你值得拥有!
基本登录方案
在说如何使用 JSON 登录之前,我们还是先来看看基本的登录吧,本文为了简单,SpringSecurity 在使用中就不连接数据库了,直接在内存中配置用户名和密码,具体操作步骤如下:
创建 Spring Boot 工程
首先创建 SpringBoot 工程,添加 SpringSecurity 依赖,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
添加 Security 配置
创建 SecurityConfig,完成 SpringSecurity 的配置,如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser(“zhangsan”).password(“$2a$10$2O4EwLrrFPEboTfDOtC0F.RpUMk.3q3KvBHRx7XXKUMLBGjOOBs8q”).roles(“user”);
}

@Override
public void configure(WebSecurity web) throws Exception {
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl(“/doLogin”)
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
RespBean ok = RespBean.ok(“ 登录成功!”,authentication.getPrincipal());
resp.setContentType(“application/json;charset=utf-8”);
PrintWriter out = resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(ok));
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
RespBean error = RespBean.error(“ 登录失败 ”);
resp.setContentType(“application/json;charset=utf-8”);
PrintWriter out = resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(error));
out.flush();
out.close();
}
})
.loginPage(“/login”)
.permitAll()
.and()
.logout()
.logoutUrl(“/logout”)
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
RespBean ok = RespBean.ok(“ 注销成功!”);
resp.setContentType(“application/json;charset=utf-8”);
PrintWriter out = resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(ok));
out.flush();
out.close();
}
})
.permitAll()
.and()
.csrf()
.disable()
.exceptionHandling()
.accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest req, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {
RespBean error = RespBean.error(“ 权限不足,访问失败 ”);
resp.setStatus(403);
resp.setContentType(“application/json;charset=utf-8”);
PrintWriter out = resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(error));
out.flush();
out.close();
}
});

}
}
这里的配置虽然有点长,但是很基础,配置含义也比较清晰,首先提供 BCryptPasswordEncoder 作为 PasswordEncoder,可以实现对密码的自动加密加盐,非常方便,然后提供了一个名为 zhangsan 的用户,密码是 123,角色是 user,最后配置登录逻辑,所有的请求都需要登录后才能访问,登录接口是 /doLogin,用户名的 key 是 username,密码的 key 是 password,同时配置登录成功、登录失败以及注销成功、权限不足时都给用户返回 JSON 提示,另外,这里虽然配置了登录页面为 /login,实际上这不是一个页面,而是一段 JSON,在 LoginController 中提供该接口,如下:
@RestController
@ResponseBody
public class LoginController {
@GetMapping(“/login”)
public RespBean login() {
return RespBean.error(“ 尚未登录,请登录 ”);
}
@GetMapping(“/hello”)
public String hello() {
return “hello”;
}
}
这里 /login 只是一个 JSON 提示,而不是页面,/hello 则是一个测试接口。
OK,做完上述步骤就可以开始测试了,运行 SpringBoot 项目,访问 /hello 接口,结果如下:

此时先调用登录接口进行登录,如下:

登录成功后,再去访问 /hello 接口就可以成功访问了。
使用 JSON 登录
上面演示的是一种原始的登录方案,如果想将用户名密码通过 JSON 的方式进行传递,则需要自定义相关过滤器,通过分析源码我们发现,默认的用户名密码提取在 UsernamePasswordAuthenticationFilter 过滤器中,部分源码如下:
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = “username”;
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = “password”;

private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher(“/login”, “POST”));
}

public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals(“POST”)) {
throw new AuthenticationServiceException(
“Authentication method not supported: ” + request.getMethod());
}

String username = obtainUsername(request);
String password = obtainPassword(request);

if (username == null) {
username = “”;
}

if (password == null) {
password = “”;
}

username = username.trim();

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);

// Allow subclasses to set the “details” property
setDetails(request, authRequest);

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

protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}

protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
//…
//…
}
从这里可以看到,默认的用户名 / 密码提取就是通过 request 中的 getParameter 来提取的,如果想使用 JSON 传递用户名密码,只需要将这个过滤器替换掉即可,自定义过滤器如下:
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest = null;
try (InputStream is = request.getInputStream()) {
Map<String,String> authenticationBean = mapper.readValue(is, Map.class);
authRequest = new UsernamePasswordAuthenticationToken(
authenticationBean.get(“username”), authenticationBean.get(“password”));
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken(
“”, “”);
} finally {
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
else {
return super.attemptAuthentication(request, response);
}
}
}
这里只是将用户名 / 密码的获取方案重新修正下,改为了从 JSON 中获取用户名密码,然后在 SecurityConfig 中作出如下修改:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.and().csrf().disable();
http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType(“application/json;charset=utf-8”);
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.ok(“ 登录成功!”);
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
});
filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType(“application/json;charset=utf-8”);
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error(“ 登录失败!”);
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
});
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
将自定义的 CustomAuthenticationFilter 类加入进来即可,接下来就可以使用 JSON 进行登录了,如下:

好了,本文就先介绍到这里,有问题欢迎留言讨论。Java 通关秘笈小程序,视频教程、学习资料、重点知识一网打尽,你值得拥有!

退出移动版