Spring securty<三> 认证案例代码
文章目录
本地项目的基础环境
环境 | 版本 |
---|---|
jdk | 1.8.0_201 |
maven | 3.6.0 |
Spring-boot | 2.3.3.RELEASE |
1、简介
spring security是一个提供身份验证、授权和防止常见攻击的框架,它对命令式和反应式应用程序都有一流的支持,是保护基于Spring的应用程序的事实上的标准。
详细可以参看《spring security官网》
2、认证(登录)
通过之前的两篇文章的介绍,应该也比较清楚了基本的概念了安全框架里的核心的概念了,从这篇开始,主要开始细化讲代码层面上的开发了;在权限框架中,认证这个部分,也算是最难的了,之后的几篇,也是主要讲述认证相关的。
3、构建项目
新建项目badger-spring-securty-3
3.1、pom文件
为了方便,在pom文件中,我加入了swagger-ui
的相关组件,以及json相关的包
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath />
</parent>
<groupId>com.badger</groupId>
<artifactId>badger-spring-security-3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>badger-spring-security-3</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<swagger.version>2.8.0</swagger.version>
</properties>
<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>
<!-- 用于自动生成 API 文档 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2、springboot 主启动类以及演示的controller接口
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SecurityApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SecurityApplication.class, args);
}
}
3.3、swagger-ui的配置类
/**
* 基于Swagger生成API文档
* @author: liqi
*/
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
static final String TEST_TOKEN_101 = "";
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.basePackage("com.badger.spring.boot.security")).paths(PathSelectors.any())
.build().globalOperationParameters(parameters());
}
private List<Parameter> parameters() {
ParameterBuilder ticketPar = new ParameterBuilder();
ticketPar.name("Authorization");
ticketPar.description("验证的token,测试使用");
ticketPar.modelRef(new ModelRef("string"));
ticketPar.defaultValue(TEST_TOKEN_101);
ticketPar.parameterType("header");
ticketPar.required(false);
return Arrays.asList(ticketPar.build());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("安全框架").description("内部接口").version("1.0.0-RELEASE").build();
}
}
3.4、统一返回的处理类
@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);
}
}
3.5、WebSecurity 配置类
package com.badger.spring.boot.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@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");
http.csrf().disable();
}
}
1、@EnableWebSecurity
注解,开启Web的Security的注解
2、配置一个默认用户,帐号为test
,密码为123456
@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");
}
3、基础帐号密码登录配置
@Autowired
private AuthenticationSuccessHandler successHandler;
@Autowired
private AuthenticationFailureHandler failureHandler;
@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");
http.csrf().disable();
}
全局的异常处理器
排除一些css、js、swagger-ui的接口、样式等
这里需要登录成功的处理器,以及登录失败的处理器,下一个小节贴基础代码;
3.6、拒绝访问处理器
/**
* 全局异常处理器
* @author liqi
*/
@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();
}
}
3.7、未认证的全局处理器
/**
* 未认证的全局处理器
* 没有认证时,在这里处理结果,不要重定向
* @author liqi
*
*/
@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();
}
}
3.8、登录成功的处理器
/**
* 登录成功,事件处理器
* @author liqi
*/
@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();
}
}
3.9、认证失败的处理器
/**
* 登录失败处理器
* @author liqi
*/
@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();
}
}
3.10、最后一个swagger-ui的登录接口测试类
@RestController
@Api(tags = { "登录接口" })
public class LoginController {
@ApiOperation(value = "账号密码登陆")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名", dataType = "String", paramType = "query", defaultValue = "test"),
@ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", defaultValue = "123456") })
@PostMapping("/auth/login")
public void login(@RequestParam(name = "username", required = true) String username,
@RequestParam(name = "password", required = true) String password) {
}
}
可以看到,这个登录接口,里面啥的都没有,只是定义了参数,并没有返回,实际业务,也没有走这个接口,而是通过Security
的拦截器链,来处理的,定义这个接口,也只是为了,swagger-ui可以扫描到,好调用,如果使用postMain调用,这个接口,可以完全不用写;
设置的默认密码,也跟上述一致,user/123456
4、测试验证
启动主启动类,打开swagger-ui页面
http://localhost:8080/swagger-ui.html#/登录接口
登录正确的帐号密码
{
"code": 200,
"message": "test"
}
错误的密码
{
"code": 400,
"message": "Bad credentials"
}
可以看到,测试成功了;
详细的代码,可以查看《码云》