1背景
简单先说一下需求吧,这样也好让看的人知道到底适不适合自己。
实现自定义的登录认证。
登录成功,生成token并将token 交由redis管理。
登录后对用户访问的接口进行接口级别权限认证。
springSecurity提供的注解权限校验适合的场景是系统中仅有固定的几个角色,且角色的凭证不可修改(如果修改需要改动代码)。
@PreAuthorize(“hasAuthority(‘ROLE_TELLER’)”)
public Account post(Account account, double amount);
注:ROLE_TELLER是写死的。
后端系统的访问请求有以下几种类型:
登录、登出(可自定义url)
匿名用户可访问的接口(静态资源,demo示例等)
其他接口(在登录的前提下,继续判断访问者是否有权限访问)
2环境搭建
依赖引入,包括springSecurity、redis、redis session需要的依赖:
org.springframework.boot spring-boot-starter-security 2.3.4.RELEASE org.springframework.session spring-session-data-redis 2.3.1.RELEASE org.springframework.boot spring-boot-starter-data-redis 2.3.4.RELEASE 注: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”);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
}
图片
注:证明可以访问静态资源不会被拦截
自定义登录认证
springSecurity是基于过滤器进行安全认证的。
我们需要自定义:
登录过滤器:负责过滤登录请求,再交由自定义的登录认证管理器处理。
登录成功处理类:顾名思义,登录成功后的一些处理(设置返回信息提示“登录成功!”,返回数据类型为json)。
登录失败处理类:类似登录成功处理类。Ps:登录成功处理类和失败处理类有默认的实现可以不自定义。但是建议自定义,因为返回的信息为英文,一般情况不符合要求。
登录认证管理器:根据过滤器传过来的登录参数,进行登录认证,认证后授权。
新建登录成功处理类
需要实现 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
}
新建登录失败处理类
实现 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
}
新建登录认证管理器
实现 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 encodedP