security学习案例总结
前言
博客一
# pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
# yml
server:
port: 8080
# 配置类配置用户名和密码
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("test").password(passwordEncoder().encode("123456"))
.authorities("admin");
}
}
- 测试
# 浏览器访问 http://localhost:8080/hello
# 输入用户名和密码test:123456
博客二
- 参考
- 新建项目demo02,在demo01的基础上开发
# 在WebSecurityConfig配置类中添加如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
// 1、表单操作 表单请求成功处理器、失败处理器;与loginPage冲突,配置后,loginPage不生效
FormLoginConfigurer<HttpSecurity> formLogin = http.formLogin();
formLogin.loginPage(null);// 自定义登录页的路由(静态页面,或者mvc视图式的页面地址)
formLogin.loginProcessingUrl(null);// 登录接口的路由地址:必须为post请求的接口地址
formLogin.isCustomLoginPage();// 是否为自定义登录页,true为自定义的登录页;
formLogin.passwordParameter(null);// 登录的表单的 密码的字段名称
formLogin.usernameParameter(null);// 登录的表单的 用户名的字段名称
formLogin.successForwardUrl(null);// 登录成功跳转的url
formLogin.successHandler(null);// 登录成功的拦截器
formLogin.defaultSuccessUrl(null);// 默认的成功的url
formLogin.failureForwardUrl(null);// 失败跳转的url
formLogin.failureHandler(null);// 失败的拦截器
formLogin.failureUrl(null);// 失败的url
formLogin.permitAll();// 放开不拦截,默认为true
formLogin.permitAll(false);
formLogin.disable();// 关闭表单的登录(其实只是拦截器链路上的一个拦截器)
formLogin.init(http);// 初始化方法
formLogin.authenticationDetailsSource(null);
formLogin.addObjectPostProcessor(null);
formLogin.withObjectPostProcessor(null);
HttpSecurity and = formLogin.and();
formLogin.setBuilder(and);
formLogin.configure(http);
http.addFilter(null);
http.addFilterAfter(null, null);
http.addFilterAt(null, null);
AnonymousConfigurer<HttpSecurity> anonymous = http.anonymous();
http.anonymous(null);
http.cors();
http.cors(null);
http.csrf();
http.csrf(null);
http.headers();
http.headers(null);
http.httpBasic();
http.httpBasic(null);
http.logout().addLogoutHandler(null);
http.logout(null);
http.oauth2Client();
http.oauth2Client(null);
http.oauth2Login();
http.oauth2Login(null);
http.oauth2ResourceServer();
http.oauth2ResourceServer(null);
http.rememberMe();
http.rememberMe(null);
http.sessionManagement();
http.sessionManagement(null);
http.exceptionHandling();
http.exceptionHandling(null);
http.userDetailsService(null);
http.authenticationProvider(null);
http.antMatcher(null);
http.apply(null);
http.authorizeRequests().antMatchers("/**/*.js", "/**/*.css").permitAll().anyRequest().hasRole("admin");
http.authorizeRequests();
http.authorizeRequests(null);
http.build();
}
- 表单登录选项: 表单请求成功处理器、失败处理器;与loginPage冲突,配置后,loginPage不生效
formLogin.loginPage(null);//自定义登录页的路由(静态页面,或者mvc视图式的页面地址)
formLogin.loginProcessingUrl(null);//登录接口的路由地址:必须为post请求的接口地址
formLogin.isCustomLoginPage();// 是否为自定义登录页,true为自定义的登录页;
formLogin.passwordParameter(null);// 登录的表单的 密码的字段名称
formLogin.usernameParameter(null);//登录的表单的 用户名的字段名称
formLogin.successForwardUrl(null);// 登录成功跳转的url
formLogin.successHandler(null);// 登录成功的拦截器
formLogin.defaultSuccessUrl(null);// 默认的成功的url
formLogin.failureForwardUrl(null);// 失败跳转的url
formLogin.failureHandler(null);// 失败的拦截器
formLogin.failureUrl(null);// 失败的url
formLogin.permitAll();// 放开不拦截,默认为true
formLogin.permitAll(false);
formLogin.disable();// 关闭表单的登录(其实只是拦截器链路上的一个拦截器)
//其他配置项
formLogin.init(http);// 初始化方法
formLogin.authenticationDetailsSource(null);
formLogin.addObjectPostProcessor(null);
formLogin.withObjectPostProcessor(null);
HttpSecurity and = formLogin.and();
formLogin.setBuilder(and);
formLogin.configure(http);
- 过滤器
http.addFilter(null);// 增加一个拦截器
http.addFilterAfter(null, null);//增加一个拦截器 在xxx拦截器之后
http.addFilterAt(null, null);//增加一个拦截器 在xxx拦截器之前
- 其他
# 匿名用户
http.anonymous();
# 跨域
http.cors();
http.cors(null);
# csrf
http.csrf();
http.csrf(null);
# httpbasic,前后端分离时关闭
http.httpBasic();
http.httpBasic(null);
# 登出
http.logout();
http.logout(null);
http.logout().addLogoutHandler(null);//配置登出处理器
http.logout().disable();// 关闭登出操作;等jwt超时
# 记住我
http.rememberMe();
http.rememberMe(null);
# session会话管理
http.sessionManagement();
http.sessionManagement(null);
http.sessionManagement().disable();// 关闭session 管理器
# 认证处理器
http.userDetailsService(null);//帐号密码认证
http.authenticationProvider(null);//认证处理
# 鉴权管理
http.authorizeRequests();
http.authorizeRequests(null);
# 异常处理
http.exceptionHandling();
http.exceptionHandling(null);
- oauth2
http.oauth2Client();
http.oauth2Client(null);
http.oauth2Login();
http.oauth2Login(null);
http.oauth2ResourceServer();
http.oauth2ResourceServer(null);
- 启动报错
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-04-05 19:46:28.199 ERROR 22748 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalArgumentException: Pattern cannot be null or empty
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:655) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
博客三
- 参考
- 启动项目报错:Error creating bean with name 'xmlModelPlugin': Lookup method resolution failed
- 解决方案:注释swagger2相关的代码
- pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
- 返回数据工具类
@Data
public class Result {
private Integer code;
private String message;
private Result(Integer code, String messgae) {
this.code = code;
this.message = messgae;
}
public static Result success(String message) {
return new Result(200, message);
}
public static Result result(Integer code, String messgae) {
return new Result(code, messgae);
}
}
- security配置类
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 放行的接口
private static final String[] EXCLUDE_URLS = { "/**/*.js", "/**/*.css", "/**/*.jpg", "/**/*.png", "/**/*.gif",
"/v2/**", "/errors", "/error", "/favicon.ico", "/swagger-ui.html/**", "/swagger-ui/**", "/webjars/**",
"/swagger-resources/**", "/auth/login" };
@Autowired
private AuthenticationSuccessHandler successHandler;
@Autowired
private AuthenticationFailureHandler failureHandler;
@Autowired
AccessDeniedHandler deniedHandler;
@Autowired
AuthenticationEntryPoint entryPoint;
// 加密方式
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 配置用户名、密码
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("test").password(passwordEncoder().encode("123456"))
.authorities("admin");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 全局异常配置
http.exceptionHandling().accessDeniedHandler(deniedHandler).authenticationEntryPoint(entryPoint);
// 放行的路由接口
http.authorizeRequests().antMatchers(EXCLUDE_URLS).permitAll();
// 1、表单操作
FormLoginConfigurer<HttpSecurity> formLogin = http.formLogin();
// 表单请求成功处理器、失败处理器;与loginPage冲突,配置后,loginPage不生效
formLogin.successHandler(successHandler).failureHandler(failureHandler);
// 表单提交的post请求地址,用户参数名称
formLogin.loginProcessingUrl("/auth/login");
// 关闭csrf防护
http.csrf().disable();
}
}
- 拒绝访问处理器
@Component
public class DeniedHandler implements AccessDeniedHandler {
@SuppressWarnings("deprecation")
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
String error = "请求Url:" + request.getRequestURI() + " 鉴权失败:" + accessDeniedException.getMessage();
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
PrintWriter writer = response.getWriter();
writer.print(JSON.toJSONString(Result.result(HttpStatus.UNAUTHORIZED.value(), error)));
writer.flush();
writer.close();
}
}
- 未认证处理器
@Component
public class EntryPoint implements AuthenticationEntryPoint {
@SuppressWarnings("deprecation")
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
String error = "请求Url:" + request.getRequestURI() + " 认证失败:" + authException.getMessage();
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
PrintWriter writer = response.getWriter();
writer.print(JSON.toJSONString(Result.result(HttpStatus.UNAUTHORIZED.value(), error)));
writer.flush();
writer.close();
}
}
- 认证成功处理器
@Component
@Slf4j
public class SuccessHandler implements AuthenticationSuccessHandler {
@SuppressWarnings("deprecation")
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
log.info("登录成功:{}", authentication.getPrincipal());
PrintWriter writer = response.getWriter();
writer.print(JSON.toJSONString(Result.success(authentication.getPrincipal().toString())));
writer.flush();
writer.close();
}
}
- 认证失败处理器
@Component
public class FailureHandler implements AuthenticationFailureHandler {
private Logger log = LoggerFactory.getLogger(getClass());
@SuppressWarnings("deprecation")
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
log.info("登录失败,{}", exception.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
PrintWriter writer = response.getWriter();
writer.print(JSON.toJSONString(Result.result(400, exception.getMessage())));
writer.flush();
writer.close();
}
}
- 登录接口编写
@RestController
public class LoginController {
@PostMapping("/auth/login")
public void login(@RequestParam(name = "username", required = true) String username,
@RequestParam(name = "password", required = true) String password) {
}
}
- 测试
博客五
-
新建demo04,在demo03的基础上开发
-
使用用户名+密码或手机号+密码或者邮箱+密码登录,但并没有构建Provider
-
认证流程
# 首先发送请求会进入AuthenticationFilter,由AuthenticationFilter生成一个UserNamepasswordAuthenticationToken
# 之后UserNamepasswordAuthenticationToken进入AuthenticationManager
# AuthenticationManager主要进行认证
# AuthenticationManager中有多个AuthenticationProviders
# 每个AuthenticationProviders是不同的认证机制
# 之后通过UserDetailsService查询数据库获取用户信息
# 最后按原来的流程将认证成功后的用户信息放到Authentication中
1. 向手机发送手机验证码,使用第三方短信平台 SDK 发送,如: 阿里云短信服务(阿里大于)
2. 登录表单输入短信验证码
3. 使用自定义过滤器 MobileValidateFilter
4. 当验证码校验通过后,进入自定义手机认证过滤器 MobileAuthenticationFilter 校验手机号是否存在
5. 自定义 MobileAuthenticationToken 提供给 MobileAuthenticationFilter
6. 自定义 MobileAuthenticationProvider 提供给 ProviderManager 处理
7. 创建针对手机号查询用户信息的 MobileUserDetailsService ,交给 MobileAuthenticationProvider
8. 自定义 MobileAuthenticationConfig 配置类将上面组件连接起来,添加到容器中
9. 将 MobileAuthenticationConfig 添加到 SpringSecurityConfig 安全配置的过滤器链上
- UsernamePasswordAuthenticationProvider
@Slf4j
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
@Getter
@Setter
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
public UsernamePasswordAuthenticationProvider(UserDetailsService userDetailsService) {
super();
this.userDetailsService = userDetailsService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取表单用户名
String username = (String) authentication.getPrincipal();
// 获取表单用户填写的密码
String password = (String) authentication.getCredentials();
UserDetails userDetails;
try {
userDetails = userDetailsService.loadUserByUsername(username);
} catch (Exception e) {
// 异常不是鉴权异常的时候,异常无法向上抛出,异常处理的controller无法返回默认异常,需要把异常处理成鉴权异常
throw new AuthenticationServiceException(e.getMessage());
}
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
log.info("当前登录人:{},当前登录密码:{}", username, password);
throw new BadCredentialsException("用户密码不正确");
}
return new UsernamePasswordAuthenticationToken(username, null, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
// 帐号密码登录使用的校验器
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
- SecurityConfig
@Configuration
public class SecurityConfig {
static final List<UserEntity> USER_LIST = new ArrayList<>();
static {
for (int i = 1; i < 6; i++) {
UserEntity userEntity = new UserEntity();
userEntity.setId(i);
userEntity.setName("测试人员" + i);
userEntity.setUsername("ceshi_" + i);
// 密码使用 PasswordEncoder 类对123456 加密之后的结果
userEntity.setPassword("$2a$10$D1q09WtH./yTfFTh35n0k.o6yZIXwxIW1/ex6/EjYTF7EiNxXyF7m");
userEntity.setEmail("100" + i + "@qq.com");
userEntity.setPhone("186xxxx351" + i);
USER_LIST.add(userEntity);
}
}
/************************帐号密码登录**********************/
@Bean
public UserDetailsService usernamePasswordUserDetails() {
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity user = null;
for (UserEntity userEntity : USER_LIST) {
if (username.equals(userEntity.getUsername()) || username.equals(userEntity.getPhone())
|| username.equals(userEntity.getEmail())) {
user = userEntity;
}
}
if (user != null) {
return new SystemUserDetails(user.getUsername(), user.getPassword(), user, null);
}
throw new UsernameNotFoundException("用户未注册,请先注册");
}
};
}
@Bean
public AuthenticationProvider usernamePasswordAuthenticationProvider() {
return new UsernamePasswordAuthenticationProvider(usernamePasswordUserDetails());
}
}
- 启动项目后,curl测试通过
curl -X POST "http://localhost:8080/auth/login?username=ceshi_1&password=123456" -H "accept: */*"
curl -X POST "http://localhost:8080/auth/login?username=1001@qq.com&password=123456" -H "accept: */*"
curl -X POST "http://localhost:8080/auth/login?username=186xxxx3511&password=123456" -H "accept: */*"