背景
简单先说一下需求吧,这样也好让看的人知道到底适不适合自己。
1、实现自定义的登录认证。
2、登录成功,生成token并将token 交由redis管理。
3、登录后对用户访问的接口进行接口级别权限认证。
springSecurity提供的注解权限校验适合的场景是系统中仅有固定的几个角色,且角色的凭证不可修改(如果修改需要改动代码)。
@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
注:ROLE_TELLER是写死的。
后端系统的访问请求有以下几种类型:
1、登录、登出(可自定义url)
2、匿名用户可访问的接口(静态资源,demo示例等)
3、其他接口(在登录的前提下,继续判断访问者是否有权限访问)
环境搭建
依赖引入,包括springSecurity、redis、redis session需要的依赖:
<!--springSecurity安全框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<!-- 默认通过SESSIONId改为通过请求头与redis配合验证session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<!--redis支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
注:springBoot版本也是2.3.4.RELEASE,如果有版本对应问题,自行解决。有用到swagger,为了便于测试。
新建springSecurity配置类
新建 WebSecurityConfig.java 继承自 WebSecurityConfigurerAdapter,过滤匿名用户可访问的接口。
WebSecurityConfig作为springSecurity的主配置文件。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* Swagger等静态资源不进行拦截
*/
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/error",
"/webjars/**",
"/resources/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v2/api-docs");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//配置一些不需要登录就可以访问的接口
.antMatchers("/demo/**", "/about/**").permitAll()
//任何尚未匹配的URL只需要用户进行身份验证
.anyRequest().authenticated()
.and()
.formLogin()//允许用户进行基于表单的认证
.loginPage("/mylogin");
}
}
注:证明可以访问静态资源不会被拦截
自定义登录认证
springSecurity是基于过滤器进行安全认证的。
我们需要自定义:
1、登录过滤器:负责过滤登录请求,再交由自定义的登录认证管理器处理。
2、登录成功处理类:顾名思义,登录成功后的一些处理(设置返回信息提示“登录成功!”,返回数据类型为json)。
3、登录失败处理类:类似登录成功处理类。Ps:登录成功处理类和失败处理类有默认的实现可以不自定义。但是建议自定义,因为返回的信息为英文,一般情况不符合要求。
4、登录认证管理器:根据过滤器传过来的登录参数,进行登录认证,认证后授权。
新建登录成功处理类
需要实现 AuthenticationSuccessHandler
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationSuccessHandler.class);
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
//登录成功返回的认证体,具体格式在后面的登录认证管理器中
String responseJson = JackJsonUtil.object2String(ResponseFactory.success(authentication));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("登录成功!");
}
response.getWriter().write(responseJson);
}
}
新建登录失败处理类
实现AuthenticationFailureHandler
@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationFailureHandler.class);
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
String errorMsg;
if (StringUtils.isNotBlank(e.getMessage())) {
errorMsg = e.getMessage();
} else {
errorMsg = CodeMsgEnum.LOG_IN_FAIL.getMsg();
}
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
String responseJson = JackJsonUtil.object2String(ResponseFactory.fail(CodeMsgEnum.LOG_IN_FAIL,errorMsg));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("认证失败!");
}
response.getWriter().write(responseJson);
}
}
新建登录认证管理器
实现 AuthenticationProvider ,负责具体的身份认证(一般数据库认证,在登录过滤器过滤掉请求后传入)
@Component
public class UserVerifyAuthenticationProvider implements AuthenticationProvider {
private PasswordEncoder passwordEncoder;
@Autowired
private UserService userService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = (String) authentication.getPrincipal(); // Principal 主体,一般指用户名
String passWord = (String) authentication.getCredentials(); //Credentials 网络凭证,一般指密码
//通过账号去数据库查询用户以及用户拥有的角色信息
UserRoleVo userRoleVo = userService.findUserRoleByAccount(userName);
//数据库密码
String encodedPassword = userRoleVo.getPassWord();