2021SC@SDUSC
学生程序设计能力提升平台 Spring Sercurity的应用
Spring security的使用
spring security 的核心功能主要包括:
- 认证 (你是谁)
- 授权 (你能干什么)
- 攻击防护 (防止伪造身份)
其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
Configration-http/https
首先继承WebSecurityConfigurerAdapter重写configure
@Override
protected void configure(HttpSecurity http) throws Exception {
//授权
http.authorizeRequests()
.antMatchers("/user/login", "/user/admin/login", "/user/register", "/user/logout").permitAll()
.anyRequest().authenticated()
.and().csrf().disable() //CSRF禁用,因为不使用session
.sessionManagement().disable() //禁用session
.formLogin().disable() //禁用form登录
.cors()
.and()
.addFilterAfter(new OptionsRequestFilter(), CorsFilter.class)
//添加登录filter
.apply(new JsonLoginConfig<>())
.loginSuccessHandler(usernameLoginSuccessHandler())
.and()
//添加token的filter
.apply(new JwtLoginConfig<>())
.tokenValidSuccessHandler(jwtRefreshSuccessHandler())
.permissiveRequestUrls("/logout")
.and()
//使用默认的logoutFilter
.logout()
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()) //logout成功后返回200
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
分析项目中的代码
-
允许未经授权即可访问的接口
http.authorizeRequests() .antMatchers("/user/login", "/user/admin/login", "/user/register", "/user/logout").permitAll()
这一段代码允许了所有不经过SpringSercurity即可访问访问的接口,包含登录,注册等。利用登录注册的过程是本省就不要任何权限就能做的。
-
.anyRequest().authenticated()
标明除此上一段代码排除的白名单外所有的接口均是需要鉴权的 -
禁用csrf以及session
and().csrf().disable() //CSRF禁用,因为不使用session .sessionManagement().disable() //禁用session .formLogin().disable() //禁用form登录
这里禁用CSRF的愿意是因为没有使用session因此不会存留cookie在本地,那么项目使用什么来鉴别神的的呢,我们下面再谈
Configration-cors
在web网页开发中最长见到的问题就是cors问题,跨域资源问题,网页和服务器几乎不能出现在统一ip的同一端口上,因此是必要运训cors,对于资源服务器,可能只允许固定的ip访问,但是对于应用服务器应该允许所有的ip访问服务器
-
这一行标记使用spring sercurity的cors
http.authorizeRequests().cors()
-
我们查看SpringSercurity源码
cors使用的方法是调用了getOrApply方法新建了一个CorsConfigurer
public CorsConfigurer<HttpSecurity> cors() throws Exception { return getOrApply(new CorsConfigurer<>()); }
getOrApply()
方法主要是应用配置,而通过new CorsConfigurer<>()
发现只是一个空的构造函数也不是我们需要的接着我们来看getOrApply()方法
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer) throws Exception { C existingConfig = (C) getConfigurer(configurer.getClass()); if (existingConfig != null) { return existingConfig; } return apply(configurer); }
首先该方法调用getConfigurer()尝试获取系统中已经存在的configerer
public <C extends SecurityConfigurer<O, B>> C getConfigurer(Class<C> clazz) { List<SecurityConfigurer<O, B>> configs = this.configurers.get(clazz); if (configs == null) { return null; } Assert.state(configs.size() == 1, () -> "Only one configurer expected for type " + clazz + ", but got " + configs); return (C) configs.get(0); }
调用
getConfigurer()
方会从configs中获取cors的配置,可以看出SpringSercurity并没有对cors做特殊处理,只是给返回加上了可以cors的请求方式另外注意
.addFilterAfter(new OptionsRequestFilter(), CorsFilter.class)
是必须的SpringSercurity会在调用cors之前调用一次Options请求public class OptionsRequestFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (request.getMethod().equals("OPTIONS")) { response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,HEAD"); response.setHeader("Access-Control-Allow-Headers", response.getHeader("Access-Control-Request-Headers")); return; } filterChain.doFilter(request, response); } }
cors由统一的拦截器配置,在项目中利用了filter+bean+config的方式实现
@Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration().applyPermitDefaultValues(); configuration.addExposedHeader("Authorization"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }
利用@Bean注入Cors配置
首先初始化默认的操作,接着统一配置暴漏的响应头,最后设置所有的接口均被配置来实现所有的接口均能够通过跨域请求访问
Configuration-login/logout
SpringSecurity自带的登录活注销界面,虽然比较美观,但是往往是不符合实际需求的,因此需要额外的定制登录或者注册接口
.apply(new JsonLoginConfig<>())
.loginSuccessHandler(usernameLoginSuccessHandler())
.and()
//添加token的filter
.apply(new JwtLoginConfig<>())
.tokenValidSuccessHandler(jwtRefreshSuccessHandler())
.permissiveRequestUrls("/logout")
.and()
//使用默认的logoutFilter
.logout()
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()); //logout成功后返回200
项目中使用两种不同的登录方式,通过用户名密码登录,或者是用过token直接登录,那么就需要根据不同的情况进行不同的配置
退出登录的是否依然是通过鉴权链,将携带的token加入到黑名单数据库中实现token的失效
登录成功的请求将会根据登录的验证返回颁发合法的json web token签名,实现登录功能
登录成功之后会出发handler,handler将角色信息和身份信息一并写入token中,并返回签证的token
public class UsernameLoginSuccessHandler implements AuthenticationSuccessHandler {
public UsernameLoginSuccessHandler() { }
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
//生成token,并把token加密相关信息缓存
//生成以','分割的角色信息
String roles = authentication.getAuthorities().stream()
.map(Object::toString).collect(Collectors.joining(","));
String token = JwtUtil.createToken(authentication.getName(), ((PtaUser) authentication.getPrincipal()).getUserId(), roles);
response.setHeader("Authorization","Bearer " + token);
response.setCharacterEncoding("UTF-8");
try {
response.getWriter().print(JSON.toJSONString(ResultEntity.data(token)));
} catch (IOException e) {
e.printStackTrace();
}
}
}