Spring Security
Spring Security 笔记
Security 底层是一个过滤器链,主要分为以下三个
- FilterSecurityInterceptor : 方法级的过滤器,位于过滤器链最底部
- ExceptionTranslationFilter: 异常处理过滤器,处理认证授权过程中抛出的异常
- UsernamePasswordAuthenticationFilter: 对 /login 的 Post 请求拦截,校验表单中的用户名和密码
加载过程
DelegatingFilterProxy: doFilter() => initDelegate() 此方法目的是拿到 FilterChainProxy => invokeDelegate() => FilterChainProxy.doFilter() => FilterChainProxy.doFilterInternal()
两个重要接口
UserDetailsService: 实现 loadUserByUsername() 方法,自定义逻辑控制用户认证(查数据库等操作)
PasswordEncoder: 数据加密接口,有三个方法
public interface PasswordEncoder {
/**
* 按照特定的解析规则进行密码解析
*/
String encode(CharSequence var1);
/**
* 验证提交的原始密码和存储中的密码是否匹配
* @param var1 要加密的密码
* @param var2 存储的密码
*/
boolean matches(CharSequence var1, String var2);
/**
* 如果解析的密码能再次解析且达到更安全的结果返回 true,否则返回 false 默认 false
*
*/
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
注解使用
配置类上开启
@EnableGlobalMethodSecurity(securedEnabled = true,
prePostEnabled = true)
注解情况下,controller 方法上可用的注解,列如:
@Secured({"ROLE_xxx", "ROLE_yyy"})
@GetMapping("/hello")
...
// 用户具有某个角色,可以访问该路径,角色前必须加 "ROLE_···"
@Secured({"ROLE_xxx", "ROLE_yyy"})
// 在方法执行之前进行权限验证
@PreAuthorize("hasAnyAuthority('admin')")
// 在方法执行之后进行权限认证,适合验证带有返回值的权限
@PostAuthorize("hasAnyAuthority('admin')")
// 权限验证之前对传入方法的参数进行过滤
@PreFilter("filterObject.id%2 == 0 ")
// 权限验证过后对返回的数据进行过滤
@PostFilter("filterObject.userName == 'admin'")
认证过滤器和授权过滤器
UsernamePasswordAuthenticationFilter: 认证过滤器
/**
* 继承 UsernamePasswordAuthenticationFilter
* 主要重写其中的 attemptAuthentication() 方法
* 和认证成功会调用的 successfulAuthentication()
* 认证失败会调用的 unsuccessfulAuthentication()
*/
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private TokenManager tokenManager; // Token 生成解析工具
private RedisTemplate redisTemplate;
public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
this.authenticationManager = authenticationManager;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
// 获取表单提交的用户名和密码
User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 认证成功
*/
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException,ServletException {
SecurityUser user = (SecurityUser) auth.getPrincipal();
String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
// redis 存入用户权限列表
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
Map map = new HashMap<String, String>();
map.put("token", token);
res.setStatus(HttpStatus.OK.value());
res.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
// 返回 Token 到前端
new ObjectMapper().writeValue(res.getWriter(),map);
}
/**
* 认证失败
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
Map map = new HashMap<String, Object>();
// 错误信息设置
map.put("message", "...");
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
// 返回错误信息
new ObjectMapper().writeValue(response.getWriter(),map);
}
}
BasicAuthenticationFilter: 授权过滤器
/**
* 授权过滤器
*
*/
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager; // Token 生成解析工具
private RedisTemplate redisTemplate;
public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
super(authManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
logger.info("================="+req.getRequestURI());
if(req.getRequestURI().indexOf("admin") == -1) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = null;
try {
authentication = getAuthentication(req); // 获取用户权限信息
} catch (Exception e) {
// response 返回错误信息
}
if (authentication != null) {
// 权限信息不为空,放入权限上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
// response 返回没有权限信息
}
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// token置于header里
String token = request.getHeader("token");
if (token != null && !"".equals(token.trim())) {
String userName = tokenManager.getUserFromToken(token);
// 从 redis 中获取用户权限列表
List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValueList) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
if (!StringUtils.isEmpty(userName)) {
return new UsernamePasswordAuthenticationToken(userName, token, authorities);
}
return null;
}
return null;
}
}
配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private TokenManager tokenManager; // Token 生成、解析工具
private DefaultPasswordEncoder defaultPasswordEncoder;
private RedisTemplate redisTemplate;
@Autowired
public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
TokenManager tokenManager, RedisTemplate redisTemplate) {
this.userDetailsService = userDetailsService;
this.defaultPasswordEncoder = defaultPasswordEncoder;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
/**
* 配置设置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new UnauthorizedEntryPoint()) // 未授权的统一处理放式
.and().csrf().disable() // 关闭 CSRF 防护
.authorizeRequests() // 设置不需要授权的访问路径
.anyRequest().authenticated()
.and().logout().logoutUrl("/logout") // 退出路劲
.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()
// 添加认证过滤器
.addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
// 添加授权过滤器
.addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
}
/**
* 密码处理
* @param auth
* @throws Exception
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}
/**
* 配置哪些请求不拦截
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/**",
"/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
);
}
}
浙公网安备 33010602011771号