四:Spring Security 登录使用 JSON 格式数据
Spring Security 登录使用 JSON 格式数据
在使用 SpringSecurity 中,大伙都知道默认的登录数据是通过 key/value 的形式来传递的,默认情况下不支持 JSON格式的登录数据,如果有这种需求,就需要自己来解决。
1、基本登录方案
在说如何使用 JSON 登录之前,我们还是先来看看基本的登录吧,本文为了简单,SpringSecurity 在使用中就不连接数据库了,直接在内存中配置用户名和密码,具体操作步骤如下:
1.1 创建 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>
1.2 添加 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 接口就可以成功访问了。
2、使用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 进行登录了,如下:
四:Spring Security 登录使用 JSON 格式数据的更多相关文章
- Spring MVC 学习笔记11 —— 后端返回json格式数据
Spring MVC 学习笔记11 -- 后端返回json格式数据 我们常常听说json数据,首先,什么是json数据,总结起来,有以下几点: 1. JSON的全称是"JavaScript ...
- iOS开发之JSON格式数据的生成与解析
本文将从四个方面对IOS开发中JSON格式数据的生成与解析进行讲解: 一.JSON是什么? 二.我们为什么要用JSON格式的数据? 三.如何生成JSON格式的数据? 四.如何解析JSON格式的数据? ...
- 使用C#中JavaScriptSerializer类将对象转换为Json格式数据
将对象转换为json格式字符串: private JavaScriptSerializer serializer = new JavaScriptSerializer(); protected voi ...
- 使用基于Android网络通信的OkHttp库实现Get和Post方式简单操作服务器JSON格式数据
目录 前言 1 Get方式和Post方式接口说明 2 OkHttp库简单介绍及环境配置 3 具体实现 前言 本文具体实现思路和大部分代码参考自<第一行代码>第2版,作者:郭霖:但是文中讲 ...
- Java入门系列:处理Json格式数据
本节主要讲解: 1)json格式数据处理方法 2)第三方工具包的使用方法 3)java集合数据类型 [项目任务] 编写一个程序,显示未来的天气信息. [知识点解析] 为了方便后面代码的分析,先需要掌握 ...
- SQL server 存储过程 C#调用Windows CMD命令并返回输出结果 Mysql删除重复数据保留最小的id C# 取字符串中间文本 取字符串左边 取字符串右边 C# JSON格式数据高级用法
create proc insertLog@Title nvarchar(50),@Contents nvarchar(max),@UserId int,@CreateTime datetimeasi ...
- 转载 -- iOS开发之JSON格式数据的生成与解析
本文将从四个方面对IOS开发中JSON格式数据的生成与解析进行讲解: 一.JSON是什么? 二.我们为什么要用JSON格式的数据? 三.如何生成JSON格式的数据? 四.如何解析JSON格式的数据? ...
- 解析json格式数据
实现目标 读取文件中的json格式数据,一行为一条json格式数据.进行解析封装成实体类. 通过google的Gson对象解析json格式数据 我现在解析的json格式数据为: {",&qu ...
- ios网络学习------6 json格式数据的请求处理
ios网络学习------6 json格式数据的请求处理 分类: IOS2014-06-30 20:33 471人阅读 评论(3) 收藏 举报 #import "MainViewContro ...
随机推荐
- QPushButton
QPushButton 什么是QPushButton? 构造函数 常用属性和方法 QPushButton 什么是QPushButton? QPushButton是一个按键类. class Q_GUI_ ...
- Codefroces 1328E Tree Querie(dfs序)
Codefroces 1328E Tree Querie 题目 给出一棵1为根,n个节点的树,每次询问\(k_i\) 个节点,问是否存在这样一条路径: 从根出发,且每个节点在这条路径上或者距离路径的距 ...
- CentOS 7 网卡注释
TYPE=Ethernet # 网络类型为:EthernetPROXY_METHOD=none # 代理方式:关闭状态BROWSER_ONLY=no # 只是浏览器:否BOOTPROTO=static ...
- 任意文件下载漏洞的接口URL构造分析与讨论
文件下载接口的URL构造分析与讨论 某学院的文件下载接口 http://www.****.edu.cn/item/filedown.asp?id=76749&Ext=rar&fname ...
- aix5.3安装httpd服务
1.安装gcc(1)从IBM上下载 gcc-4.0.0-1.aix5.3.ppc.rpm gcc-cplusplus-4.0.0-1.aix5.3.ppc.rpm libgcc-4.0.0-1.aix ...
- tomcat版本号修改已dwr配置错误安全漏洞整改
1.tomcat版本信息泄露修改方法:tomcat6是在tomcat/lib 下使用jar xf catalina.jar 解压这个jar包会得到两个目录:META-INF和org其中org\apac ...
- Java 设置Excel条件格式(高亮条件值、应用单元格值/公式/数据条等类型)
概述 在Excel中,应用条件格式功能可以在很大程度上改进表格的设计和可读性,用户可以指定单个或者多个单元格区域应用一种或者多种条件格式.本篇文章,将通过Java程序示例介绍条件格式的设置方法,设置条 ...
- leetcode-222完全二叉树的节点个数
题目 给出一个完全二叉树,求出该树的节点个数. 说明: 完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置. ...
- 【Linux】云服务器部署宝塔linux控制面板环境
服务器购买及宝塔部署环境说明 简单记录 - 狂神的 服务器购买及宝塔部署环境说明 服务器如何购买 我们尽量趁打折的时候购买,比较便宜点!多看看有活动. 如果是学生,可以购买学生机, 学生机地址:htt ...
- 【Linux】Linux进程间通信的几种方式
一.进程间通信的目的 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间 共享数据:多个进程要操作共享数据,一个进程对共享数据 信息传递:一个进程需要向另一个进程发 ...