关于使用JSON传输数据来实现SpringSecurity用户验证的解决方案
前情摘要
-
本方案是解决WebConfigrationAdapter过时,利用新API解决的方案
-
本方案是解决传统表单提交数据,改用JSON传输数据
-
本方案使用了官方推荐的Bcytpe加密算法,将存储用户密码,解密,都使用了以上加密算法
-
最后,希望本套解决方案能够帮助您在使用SpringSecurity中解决一些问题
参考文章:【深入浅出Spring Security(五)】自定义过滤器进行前后端登录认证
ps:上面的大佬关于SpringSecurity有着深厚的理解,大家可以去学习学习
我们就废话不多说,开始我们的解决方案
我们需要干些什么?
1.引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--德鲁伊数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.18</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!--spring boot web模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!--配置文件提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--SpringSecurity依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.关于springSecurity老的表单认证
我们需要知道,老的表单认证,我们是基于UsernamePasswordFilter这个过滤器的
那我们需要做什么:新创建一个类去继承UsernamePasswordFilter,然后重写里面的方法 ——attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
JsonUsernamePasswordFilter.java
package com.hitBadminton.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
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.util.Map;
public class JsonUsernamePasswordFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
ObjectMapper objectMapper;
public JsonUsernamePasswordFilter() {
}
public JsonUsernamePasswordFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 判断请求方式是否是 POST 方式
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
// 然后判断是否是 JSON 格式的数据
if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
// 如果是的话就从 JSON 中取出用户信息进行认证
try {
//使用jackson读取json
Map<String, String> userInfo = objectMapper.readValue(request.getInputStream(), Map.class);
String username = userInfo.get(getUsernameParameter());
String password = userInfo.get(getPasswordParameter());
// 封装成Authentication
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
// 仿造父类去调用AuthenticationManager.authenticate认证就可以了
setDetails(request, authRequest);
return getAuthenticationManager().authenticate(authRequest);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 否则用父类的方式去认证
return super.attemptAuthentication(request, response);
}
}
3.在springSecurity的配置类中将这个JSONUsernamePasswordFilter替换掉原有的UsernamePasswordFilter
以前我们需要自己新建一个类去继承WebConfigurationAdepter
like this :
public class WebConfig extends WebConfigurationAdepter {
}
但是现在springSecurity升级过后,将 WebConfigrationAdepter 标注 WebConfigrationAdepter ,那么这个方法就过时了。
那我们就使用新的API去配置SpringSecurity的配置类
WebSecurityConfig.java
package com.hitBadminton.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hitBadminton.security.JsonUsernamePasswordFilter;
import com.hitBadminton.security.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
//@Configuration 可标可不标
//因为@EnableWebSecurity 已经包含了@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Autowired
ObjectMapper objectMapper;
@Autowired
UserDetailsServiceImpl userDetailsService;
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring().antMatchers(HttpMethod.POST,"/user");
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.logout()
.logoutSuccessHandler(this::onAuthenticationSuccess)
.logoutUrl("/logout")
.and()
//.addFilterAt 替换掉目标类
.addFilterAt(JsonUsernamePasswordFilter(http), UsernamePasswordAuthenticationFilter.class)
.csrf()
.disable()
.build();
}
/**
* 自定义 AuthenticationManager
*
* @param http
* @return AuthenticationManager
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
return http.
getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.and()
.build();
}
@Bean
public JsonUsernamePasswordFilter JsonUsernamePasswordFilter(HttpSecurity http) throws Exception {
JsonUsernamePasswordFilter filter = new JsonUsernamePasswordFilter(authenticationManager(http));
// 自定义 JSON 的 key
filter.setUsernameParameter("username");
filter.setPasswordParameter("password");
// 自定义接收的url,默认是login
// 此过滤器的doFilter是在AbstractAuthenticationProcessingFilter,在那里进行的url是否符合的判定
filter.setFilterProcessesUrl("/user/login");
// 设置login成功返回的JSON数据
filter.setAuthenticationSuccessHandler(this::onAuthenticationSuccess);
// 设置login失败返回的JSON数据
filter.setAuthenticationFailureHandler(this::onAuthenticationFailure);
return filter;
}
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
if (request.getRequestURI().endsWith("/login"))
out.write(objectMapper.writeValueAsString("登录成功"));
else if (request.getRequestURI().endsWith("/logout"))
out.write(objectMapper.writeValueAsString("注销成功"));
out.close();
}
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(objectMapper.writeValueAsString("登录失败"));
out.close();
}
/**
* 向容器注入passwordEncoder进行加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();//官方推荐使用BCryptPasswordEncoder,更安全!!!
}
}
4.接下来去实现我们的UserDetailsService
UserDetailsServiceImpl
package com.hitBadminton.security;
import com.hitBadminton.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (userService.getUserDetail(username) == null) {
throw new UsernameNotFoundException("用户不存在");
}
com.hitBadminton.pojo.User user = userService.getUserDetail(username);
String encodePassword = user.getUserPassword();
//这里demo随便写的权限list
List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
return new User(user.getUserId(), encodePassword, list);
}
}
PasswordUtils
package com.hitBadminton.utills;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordUtils {
public static BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
public static String encodePassword(String rawPassword) {
return bCryptPasswordEncoder.encode(rawPassword);
}
}
接下来的操作都大同小异,你们就自己实现啦~
注意事项
- 我们在使用Bcypt加密需要将password字段长度设置至少大于60
public class PasswordUtils {
public static BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
public static String encodePassword(String rawPassword) {
return bCryptPasswordEncoder.encode(rawPassword);
}
}
接下来的操作都大同小异,你们就自己实现啦~
注意事项
- 我们在使用Bcypt加密需要将password字段长度设置至少大于60
测试效果
1.登录(username & password == true)

2.登录(username & password != true)


1万+

被折叠的 条评论
为什么被折叠?



