SpringBoot--实战开发--Spring Securit
一、Spring Security简介
Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。
网址:https://spring.io/projects/spring-security
在线文档
认证过程
用户使用用户名和密码进行登录。
Spring Security 将获取到的用户名和密码封装成一个实现了 Authentication 接口的 UsernamePasswordAuthenticationToken。
将上述产生的 token 对象传递给 AuthenticationManager 进行登录认证。
AuthenticationManager 认证成功后将会返回一个封装了用户权限等信息的 Authentication 对象。
通过调用 SecurityContextHolder.getContext().setAuthentication(...) 将 AuthenticationManager 返回的 Authentication 对象赋予给当前的 SecurityContext。
二、Maven依赖
<!--spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
三、测试
- 创建控制器
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping
public String getUsers() {
return "Spring Security 测试";
}
}
-
访问接口
访问接口
默认用户:user
密码
- 禁用安全设置或者设置对应的用户和密码
在main方法上配置:
@SpringBootApplication(exclude = {
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class
})
- 配置文件中指定用户名与密码
spring.security.user.name=admin
spring.security.user.password=123456
四、自定义用户认证逻辑
- 添加配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
UserDetailsService customUserService() {
return new CustomUserDetailsService();
}
// 配置PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService()).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 表单登录
.and()
.authorizeRequests() // 对请求做授权, 定义哪些URL需要被保护、哪些不需要被保护
.anyRequest() // 任何请求,登录后可以访问
.authenticated() // 都需要身份认证
.and().csrf().disable(); // 暂时将防护跨站请求伪造的功能置为不可用
}
}
加密算法 | PasswordEncoder 实现类 |
---|---|
plaintext | PlaintextPasswordEncoder |
sha | ShaPasswordEncoder |
sha-256 | ShaPasswordEncoder,使用时new ShaPasswordEncoder(256) |
md4 | Md4PasswordEncoder |
md5 | Md5PasswordEncoder |
{sha} | LdapShaPasswordEncoder |
{ssha} | LdapShaPasswordEncoder |
- 添加自定义用户类
@Slf4j
public class CustomUserDetailsService implements UserDetailsService {
// 自带加密类
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("登录用户名:"+username);
// 根据用户名查找用户信息
/**
* 简单的对123456进行了加密的处理。我们可以进行测试,
* 发现每次打印出来的password都是不一样的,这就是配置的BCryptPasswordEncoder所起到的作用。
*/
String password = passwordEncoder.encode("123456");
// 参数:1.账号 2.密码 3.账户是否可用(删除) 4.账户是否过期 5.密码是否过期 6.账户是否被锁定(冻结)7.角色
return new User(username, password,
true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
- 测试
访问地址:http://localhost:8000/user
认证
五、单元测试
在写单元测试时,需要模拟某个用户的登录状态
在写单元测试时,需要模拟某个用户具有某个权限,但又不想改变数据库
编写单元测试时,需求完整调用某个用户的登录
@WithMockUser 模拟用户,手动指定用户名和授权
@WithAnonymousUser 模拟匿名用户
@WithUserDetails 模拟用户,给定用户名,通过自定义UserDetails来认证
@WithSecurityContext 通过SecurityContext构造器模拟用户
- 测试基类
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class BaseAdminApplicationTest {
// 模拟MVC对象,通过MockMvcBuilders.webAppContextSetup(this.wac).build()初始化。
public MockMvc mockMvc;
// 注入WebApplicationContext
@Autowired
private WebApplicationContext wac;
// 在测试开始前初始化工作
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build();
}
}
- 测试类
@Slf4j
public class AuthTest extends BaseAdminApplicationTest {
/**
* 1.登录测试
*
* @throws Exception
*/
@Test
public void testFormLoginSuccess() throws Exception {
// 测试登录成功
mockMvc
.perform(formLogin("/login").user("admin").password("123456"))
.andExpect(authenticated());
}
/**
* 2. 测试登录失败
*
* @throws Exception
*/
@Test
public void testFormLoginFail() throws Exception {
mockMvc
.perform(formLogin("/login").user("admin").password("invalid"))
.andExpect(unauthenticated());
}
/**
* 测试退出登录
*
* @throws Exception
*/
@Test
public void testLogoutFail() throws Exception {
// 测试退出登录
mockMvc.perform(logout("/logout")).andExpect(unauthenticated());
}
}
六、自定义登录认证
1.JWT依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
1. 创建JWT用户类
@Data
public class JwtUser implements UserDetails {
private Long id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
public JwtUser() {
}
// 写一个能直接使用user创建jwtUser的构造器
public JwtUser(User user) {
id = user.getId();
username = user.getUsername();
password = user.getPassword();
authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRole()));
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String toString() {
return "JwtUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", authorities=" + authorities +
'}';
}
}
2. 创建登录用户类
作用:获取表单登录信息与数据库实体不一定对应。
@Data
public class LoginUser {
private String username;
private String password;
private Integer rememberMe;
}
3. 数据库用户类
@Data
public class User {
private Long id;
private String username;
private String password;
private String role;
}
4. JWT工具类
@Slf4j
public class JwtTokenUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
private static String SECRET = "xxxxxxxx";
private static final String ISS = "echisan";
// 角色的key
private static final String ROLE_CLAIMS = "role";
// 过期时间是3600秒,既是1个小时
private static final long EXPIRATION = 60 * 60L;
// 选择了记住我之后的过期时间为7天
private static final long EXPIRATION_REMEMBER = 7 * 24 * 60 * 60L;
/**
* 创建token
*
* @param username
* @param role
* @param isRememberMe
* @return
*/
public static String createToken(String username, String role, boolean isRememberMe,String secret) {
long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
HashMap<String, Object> map = new HashMap<>();
if(!secret.isEmpty()){
SECRET=secret;
}
map.put(ROLE_CLAIMS, role);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.setIssuer(ISS)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.compact();
}
/**
* 从token中获取用户名
*
* @param token
* @return
*/
public static String getUsername(String token) {
return getTokenBody(token).getSubject();
}
/**
* 获取用户角色
*
* @param token
* @return
*/
public static String getUserRole(String token) {
return (String) getTokenBody(token).get(ROLE_CLAIMS);
}
/**
* 是否已过期
*
* @param token
* @return
*/
public static boolean isExpiration(String token) {
return getTokenBody(token).getExpiration().before(new Date());
}
/**
* 获取令牌体
*
* @param token
* @return
*/
private static Claims getTokenBody(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}
5. 创建认证过滤器
@Slf4j
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private ThreadLocal<Integer> rememberMe = new ThreadLocal<>();
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
// 设置过滤器处理地址
super.setFilterProcessesUrl("/auth/login");
}
/**
*
* @param request
* @param response
* @return
* @throws AuthenticationException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
// 获取登录信息
LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class);
log.info("登录用户:"+loginUser.getUsername()+"密码:"+loginUser.getPassword());
// 记住登录信息
rememberMe.set(loginUser.getRememberMe());
// 进行认证
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>())
);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 成功验证后调用的方法
* 如果验证成功,就生成token并返回
* @param request
* @param response
* @param chain
* @param authResult
* @throws IOException
* @throws ServletException
*/
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// 获取Jwt用户信息
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
System.out.println("用户:" + jwtUser.toString()+"密码:"+jwtUser.getPassword());
// 记住登录信息
boolean isRemember = rememberMe.get() == 1;
// 获取授权信息
String role = "";
Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
for (GrantedAuthority authority : authorities) {
role = authority.getAuthority();
}
// 创建令牌
String token = JwtTokenUtils.createToken(jwtUser.getUsername(), role, isRemember,jwtUser.getPassword());
// String token = JwtTokenUtils.createToken(jwtUser.getUsername(), false);
// 返回创建成功的token
// 但是这里创建的token只是单纯的token
// 按照jwt的规定,最后请求的时候应该是 `Bearer token`
response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.getWriter().write("认证失败, 原因: " + failed.getMessage());
}
}
6. 创建授权过滤器
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
*
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 获取请求头信息
String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
// 如果请求头中没有Authorization信息则直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有token,则进行解析,并且设置认证信息
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
super.doFilterInternal(request, response, chain);
}
/**
* 这里从token中获取用户信息并新建一个token
* @param tokenHeader
* @return
*/
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
// 获取令牌
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
// 获取用户信息
String username = JwtTokenUtils.getUsername(token);
// 获取角色信息
String role = JwtTokenUtils.getUserRole(token);
if (username != null){
return new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority(role))
);
}
return null;
}
}
- 创建配置
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 设置加密方式
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
/**
* 配置http权限认证
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
// 测试用资源,需要验证了的用户才能访问
.antMatchers("/user/**").authenticated()
.antMatchers(HttpMethod.DELETE, "/user/**").hasRole("ADMIN")
// 其它的都可以访问
.anyRequest().permitAll()
.and()
// 添加认证过滤器
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
// 添加授权过滤器
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 添加异常处理
.exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint());
}
/**
* 跨域请求配置
* @return
*/
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
- 统一认证异常处理
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "认证失败,原因:"+authException.getMessage();
response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
}
}
- 用户业务类
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 通过username在数据库中查找用户,这里模拟
User user=new User();
user.setUsername("admin");
user.setPassword(bCryptPasswordEncoder.encode("123456"));
user.setId(1L);
user.setRole("admin");
return new JwtUser(user);
}
}
- 控制器类
验证控制器:
@RestController
@RequestMapping("/auth")
@Slf4j
public class AuthController {
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@PostMapping("/register")
public String registerUser(@RequestBody Map<String,String> registerUser){
User user = new User();
user.setUsername(registerUser.get("username"));
user.setPassword(bCryptPasswordEncoder.encode(registerUser.get("password")));
user.setRole("ROLE_USER");
return "注册成功";
}
}
用户控制器
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping
public String getUsers() {
return "Spring Security 测试";
}
}
七、认证测试
-
获取令牌
localhost:8000/auth/login
获取令牌 -
资源访问
localhost:8000/user
资源访问
八、JWTToken超时刷新
九、spring security退出功能
注销默认地址:/logout
spring security实现注销功能涉及的三个核心类为LogoutFilter,LogoutHandler,LogoutSuccessHandler
LoginFilter是实现注销功能的过滤器,默认拦截/logout或者logout属性logout-url指定的url
LogoutHandler接口定义了退出登录操作的方法
LogoutSuccessHandler接口定义了注销之后的操作方法
登出处理器:
@Component
@Slf4j
public class LogoutAuthenticationHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// TODO 注意:要想在此获取authentication数据,登录成功后,必须调用一次资源,才可获取。
// 与 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 等同
log.info(authentication.getPrincipal() + "");
// 在此可以删除redis中保存的token数据
log.info("注销成功");
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write(new ObjectMapper().writeValueAsString("ok"));
}
}
十、授权表达式
表达式 | 说明 |
---|---|
permitAll | 永远返回 true |
denyAll | 永远返回 false |
anonyous | 当前用户若是匿名用户返回 true |
rememberMe | 当前用户若是 rememberMe 用户返回 true |
authenticated | 当前用户若不是匿名(已认证)用户返回 true |
fullAuthenticated | 当前用户若既不是匿名用户又不是 rememberMe 用户时返回 true |
hasRole(role) | 当前用户权限集合中若拥有指定的 role 角色权限(匹配时会在你所指定的权限前加'ROLE_',即判断是否有“ROLE_role”权限)时返回 true |
hasAnyRole(role1, role2, ...) | 当前用户权限集合中若拥有任意一个角色权限时返回 true |
hasAuthority(authority) | 当前用户权限集合中若具有 authority 权限(匹配是否有“authority”权限)时返回 true |
hasAnyAuthority(authority) | 当前用户权限集合中若拥有任意一个权限时返回 true |
hasIpAddress("192.168.1.0/24") | 发送请求的IP匹配时fanhui true |
基于角色的访问控制 RBAC 数据模型(Role-Based Access Control)
十一、Spring Security过滤器链及认证流程
spring Security功能的实现主要是由一系列过滤器链相互配合完成。
Spring Security过滤器链及认证流程
过滤器链中主要的几个过滤器及其作用:
1.SecurityContextPersistenceFilter 会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;
2.UsernamePasswordAuthenticationFilter 用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变;
3.FilterSecurityInterceptor 是用于保护Http 资源的,它需要一个AccessDecisionManager和一个AuthenticationManager 的引用。它会从 SecurityContextHolder 获取 Authentication,然后通过 SecurityMetadataSource 可以得知当前请求是否在请求受保护的资源。对于请求那些受保护的资源,如果Authentication.isAuthenticated()返回false或者FilterSecurityInterceptor
的alwaysReauthenticate 属性为 true,那么将会使用其引用的 AuthenticationManager 再认证一次,认证之后再使用认证后的 Authentication 替换 SecurityContextHolder 中拥有的那个。然后就是利用 AccessDecisionManager 进行权限的检查;
4.ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,并进行处理。
但是它只会处理两类异常:AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。
--- 如果捕获到的是 AuthenticationException,那么将会使用其对应的 AuthenticationEntryPoint 的commence()处理。在处理之前,ExceptionTranslationFilter先使用 RequestCache 将当前的HttpServerletRequest的信息保存起来,以至于用户成功登录后可以跳转到之前的界面;
--- 如果捕获到的是 AccessDeniedException,那么将视当前访问的用户是否已经登录认证做不同的处理,如果未登录,则会使用关联的 AuthenticationEntryPoint 的 commence()方法进行处理,否则将使用关联的 AccessDeniedHandler 的handle()方法进行处理。
十二、认证流程
认证流程分为登录流程和注销流程:
(注意:这里的登录方式为"帐号+密码";箭头代表整个流程执行方向)
1)登录流程
---> SecurityContextPersistenceFilter(作用在第一节已经介绍)
---> UsernamePasswordAuthenticationFilter
(先获取用户名和密码,并将其封装成UsernamePasswordToken,然后调用AuthenticationManager进行验证)
---> AuthenticationManager
(根据token类型选择合适的AuthenticationProvider来处理认证请求) [默认实现类:ProviderManager]
AuthenticationManager是一个用来处理请求的接口,它自己不直接处理认证请求,而是委托给其所配置的Authentication
Provider列表,然后会依次使用每一个 AuthenticationProvider 进行认证,如果有一个AuthenticationProvider 认证后的结果不为 null,则表示该AuthenticationProvider已经认证成功,之后的AuthenticationProvider 将不再继续认证。然后直接以该 AuthenticationProvider 的认证结果作为 ProviderManager 的认证结果。如果所有的 AuthenticationProvider 的认证结果都为null,则表示认证失败,将抛出一个 ProviderNotFoundException。
---> AuthenticationProvider
(请求认证处理) [默认实现类:DaoAuthencationProvider]
DaoAuthenticationProvider认证过程:
DaoAuthenticationProvider先调用UserDetailsService 的loadUserByUsername()方法获取UserDetails,获取后再与UsernamePasswordAuthenticationFilter获取的username和password进行比较;如果认证通过后会将该 UserDetails 赋给认证通过的 Authentication的principal,然后再把该 Authentication 存入到 SecurityContext 中。默认情况下,在认证成功后ProviderManager也将清除返回的Authentication中的凭证信息。
注意:在这里面根据需要增加[自定义关键类(UserDetailService):实现UserDetailService接口并复写loadUserByUsername()]
问:为什么AuthenticationManager不直接认证请求?
答:因为token有多种类型。比如最简单的UsernamePasswordAuthenticationToken,还有spring social的token;
---> Authentication对象
Spring Security使用一个Authentication 对象来描述当前用户的相关信息。SecurityContextHolder中持有的是当前用户的 SecurityContext,而 SecurityContext 持有的是代表当前用户相关信息的 Authentication 的引用。这个 Authentication 对象不需要我们自己去创建,在与系统交互的过程中,Spring Security会自动为我们创建相应的Authentication对象,然后赋值给当前的SecurityContext。
2)注销流程
1、使HttpSession失效;
2、清除所有已经配置的remember-me认证;
3、清除SecurityContextHolder中的user信息,并设置Authentication中的Authenticated属性为false;
4、跳转到指定url;
附:
认证提供:
/**
* @描 述:
* AuthenticationProvider(身份验证提供者) 顾名思义,可以提供一个 Authentication 供Spring Security的上下文使用
* 1. 创建CustomAuthenticationProvider类
* 2. 当 CustomAuthenticationProvider 认证成功之后,JWTLoginFilter 中的 successfulAuthentication() 方法机会执行
* 本类没有用到
*/
@Slf4j
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
/**
* 验证登录信息,若登陆成功,设置 Authentication
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取认证的用户名 & 密码
String username = authentication.getName();
String password = authentication.getCredentials().toString();
//通过用户名从数据库中查询该用户
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
//判断密码是否正确
String dbPassword = userDetails.getPassword();
String encoderPassword = bCryptPasswordEncoder.encode(password);
if (!dbPassword.equals(encoderPassword)) {
// throw new UsernameIsExitedException("密码错误");
log.info("密码错误");
}
Authentication auth = new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
return auth;
}
@Override
public boolean supports(Class<?> authentication) {
// 进行验证
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
十三、常见问题:
- Encoded password does not look like BCrypt
添加配置:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService()).passwordEncoder(passwordEncoder());
}
- Spring Security拦截器引起Java CORS跨域失败的问题
response响应头:
响应头字段名称 | 作用 |
---|---|
Access-Control-Allow-Origin | 允许访问的客户端的域名 |
Access-Control-Allow-Credentials | 是否允许请求带有验证信息,若要获取客户端域下的cookie时,需要将其设置为true。 |
Access-Control-Allow-Headers | 允许服务端访问的客户端请求头 |
Access-Control-Allow-Methods | 允许访问的HTTP请求方法 |
Access-Control-Max-Age | 用来指定预检请求的有效期(秒),在有效期内不在发送预检请求直接请求。 |
解决:
@Configuration
public class WebAppConfigurer extends WebMvcConfigurationSupport {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
}