3.7、多个安全配置共存
一、多个安全配置共存
什么叫多个安全配置共存呢?简单举例就是既能实现form表单登录,同时也支持api的rest请求的登录。
二、SecurityConfig配置类拆分
2.1、新建LoginSecurityConfig配置类
package com.moss.uaa_security.config;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @description
* @author: lwj
* @create: 2021-03-26 22:01
**/
@Order(100)
@Configuration
public class LoginSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(req -> req.anyRequest().authenticated())
// 设置无访问权限时,通过页面form表单登录的方式鉴权
.formLogin(
form -> form.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/")
.permitAll()
)
.logout(logout -> logout.logoutUrl("/perform_logout"))
.rememberMe(rememberMe -> rememberMe.tokenValiditySeconds(30 * 24 * 3600).rememberMeCookieName("someKeyForRemember"));
}
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略/error的请求。此时不走过滤器链(Security filter chain)
web.ignoring().antMatchers("/error")
// 配置默认的静态资源存放路径
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
}
2.2、SecurityConfig配置修改
package com.moss.uaa_security.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.moss.uaa_security.security.filter.RestAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
import java.util.Map;
/**
* @description
* @author: lwj
* @create: 2021-03-23 22:18
**/
@Slf4j
@Order(99)
@RequiredArgsConstructor
@EnableWebSecurity(debug = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final SecurityProblemSupport securityProblemSupport;
private final ObjectMapper objectMapper;
@Bean
PasswordEncoder passwordEncoder() {
val idForDefault = "bcrypt";
val encoders = Map.of(
idForDefault, new BCryptPasswordEncoder(),
"SHA-1", new MessageDigestPasswordEncoder("SHA-1")
);
return new DelegatingPasswordEncoder(idForDefault, encoders);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers(req -> req.antMatchers("/authorize/**", "/admin/**", "/api/**"))
// 设置框架将不创建session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exp -> exp
.accessDeniedHandler(securityProblemSupport)
.authenticationEntryPoint(securityProblemSupport)
)
.authorizeRequests(req -> req
.antMatchers("/authorize/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/api/**").hasRole("USER")
.anyRequest().authenticated())
.addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
// 设置Http Basic Auth认证方式
// .httpBasic(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("{bcrypt}$2a$10$pCSen5PBHSoPItybTfLOLuXXaj1IY/V6.eDvkuP9j0HUkrwtGgD4u")
.roles("USER", "ADMIN")
.and()
.withUser("zhangsan")
.password("{SHA-1}{1v3gR8lstbrQWb9AmSM7VMZjkc/L8T60FGeAoTyqpF0=}5157f156d83336f74c7b0e575ede3df4113ed31b")
.roles("USER")
;
}
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略校验/public/**的请求。此时不走过滤器链(Security filter chain)
web.ignoring().antMatchers("/error");
}
private RestAuthenticationFilter restAuthenticationFilter() throws Exception {
RestAuthenticationFilter restAuthenticationFilter = new RestAuthenticationFilter(objectMapper);
restAuthenticationFilter.setAuthenticationSuccessHandler(jsonAuthenticationSuccessHandler());
restAuthenticationFilter.setAuthenticationFailureHandler(jsonAuthenticationFailureHandler());
restAuthenticationFilter.setAuthenticationManager(authenticationManager());
// 将该filter应用于当前的url请求
restAuthenticationFilter.setFilterProcessesUrl("/authorize/login");
return restAuthenticationFilter;
}
private AuthenticationSuccessHandler jsonAuthenticationSuccessHandler() {
return (req, res, auth) -> {
ObjectMapper objectMapper = new ObjectMapper();
res.setStatus(HttpStatus.OK.value());
res.getWriter().println(objectMapper.writeValueAsString(auth));
log.debug("认证成功");
};
}
private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
return (req, res, exp) -> {
val objectMapper = new ObjectMapper();
res.setStatus(HttpStatus.UNAUTHORIZED.value());
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
res.setCharacterEncoding("UTF-8");
val errData = Map.of(
"title", "认证失败",
"details", exp.getMessage()
);
res.getWriter().println(objectMapper.writeValueAsString(errData));
};
}
}
2.3、注意点
这里需要注意的是在多配置类共存的情况下,需要设置@Order注解,用来顺序加载Bean。否则会冲突。