Spring Security
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的实际标准。 Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring Security的真正强大之处在于可以轻松扩展以满足自定义要求
JWT无状态的单点登录
jwt生成的token有有效期,如果没有到有效期怎么让token失效?
用户每次登录都把token对应保存到redis(username,token)
每次判断token是否有效,有效再次判断token是否和redis是否一致,不一致则token失效
流程
- 注册 username,password(加密后的)存到数据库
- 登录 通过username 和 password 比较判断是否正确,正确返回用户信息 token(jwt生成)
- 访问(需要传入token)通过token生成对应的UsernamePasswordAuthenticationToken并设置到SecurityContext
中去
- 设置某一个方法的访问权限
@PreAuthorize(“hasAuthority(‘ROLE_XYZ’)”)与@PreAuthorize(“hasRole(‘XYZ’)”)相同
用到的方法
configure(HttpSecurity http)
http.cors() .and() // 关闭csrf .csrf().disable() // 关闭session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .exceptionHandling() .accessDeniedHandler(deniedHandler) .authenticationEntryPoint(entryPoint) .and() .authorizeRequests() // 需要角色为ADMIN才能删除该资源 .antMatchers(HttpMethod.DELETE, "/tasks/**").hasAnyRole(ROLE_ADMIN) // 测试用资源,需要验证了的用户才能访问 .antMatchers("/tasks/**").authenticated() // 其他都放行了 .anyRequest().permitAll() .and() .formLogin() .loginPage("/api/user/login") .usernameParameter("user") .passwordParameter("pwd") .successHandler(successHandler) .failureHandler(failureHandler) .permitAll() .and() .logout()//默认注销行为为logout .logoutUrl("/api/user/logout") .logoutSuccessHandler(logoutSuccessHandler) .and() // 添加到过滤链中 // 先是UsernamePasswordAuthenticationFilter用于login校验 // 再通过OncePerRequestFilter,对其他请求过滤 .addFilter(new JwtPreAuthFilter(authenticationManager(), getJwtTokenUtil()));
登录 authenticationSuccessHandler loadUserByUsername
http://127.0.0.1:8087/api/user/login
//匹配 List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>(); simpleGrantedAuthorities.add(new SimpleGrantedAuthority(ROLE_ADMIN)); AdminUser user = AdminUser.getInstance(); return new User(user.getUsername(), user.getPassword(), simpleGrantedAuthorities); //返回数据 response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); String s = JwtTokenUtil.TOKEN_PREFIX+jwtTokenUtil.generateToken(SecurityUtils.getCurrentUser()); response.getWriter().write(JSONObject.toJSONString(RUtil.success(AdminUser.getInstance().setToken(s))));
通过token访问 doFilterInternal
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String tokenHeader = request.getHeader(JwtTokenUtil.TOKEN_HEADER); System.out.println("tokenHeader:" + tokenHeader); // 如果请求头中没有Authorization信息则直接放行了 if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) { chain.doFilter(request, response); return; } // 如果请求头中有token,则进行解析,并且设置认证信息 SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader)); super.doFilterInternal(request, response, chain); } /** * description: 读取Token信息,创建UsernamePasswordAuthenticationToken对象 * * @param tokenHeader * @return org.springframework.security.authentication.UsernamePasswordAuthenticationToken */ private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) { //解析Token时将“Bearer ”前缀去掉 String token = tokenHeader.replace(JwtTokenUtil.TOKEN_PREFIX, ""); String username = jwtTokenUtil.getUserNameFromToken(token); List<String> roles = jwtTokenUtil.getUserRole(token); Collection<GrantedAuthority> authorities = new HashSet<>(); if (roles != null) { for (String role : roles) { // authorities.add(new SimpleGrantedAuthority(ROLE_PRE+role)); //hasRole('admin') //hasAuthority('admin') //@PreAuthorize("hasAuthority('ROLE_XYZ')")与@PreAuthorize("hasRole('XYZ')")相同 authorities.add(new SimpleGrantedAuthority(role)); } } if (username != null) { return new UsernamePasswordAuthenticationToken(username, null, authorities); } return null; }
方法设置权限
@ApiOperation(value = "测试权限接口hasAuthority") @PostMapping(value = "/testPreAuthorize") // @PreAuthorize("hasAuthority('administrator') or hasAuthority('admin')") // @PreAuthorize("hasAuthority('administrator') and hasAuthority('admin')") //@PreAuthorize("hasAnyRole('administrator','admin')") //@PreAuthorize("hasAnyAuthority('administrator','admin')") @PreAuthorize("hasAuthority('admin')") public R<String> testPreAuthorize() { return RUtil.success("admin" + SecurityContextHolder.getContext().getAuthentication().getPrincipal()); } @ApiOperation(value = "测试权限接口") @PostMapping(value = "/testPreAuthorizeRole") @PreAuthorize("hasRole('admin')") public R<String> testPreAuthorizeRole() { return RUtil.success("admin" + SecurityContextHolder.getContext().getAuthentication().getPrincipal()); }
验证密码流程
判断用户输入的密码是否正确 原密码和加密后的密码匹配
- UserDetails userDetails(数据库存储的密码-加密后的密码) 通过 (UserDetailsService-loadUserByUsername)赋值
- UsernamePasswordAuthenticationToken authentication(前端获取到的密码) 通过过滤器(UsernamePasswordAuthenticationToken->attemptAuthentication)赋值
- 对比密码 DaoAuthenticationProvider->additionalAuthenticationChecks->
@Sup
@SuppressWarnings("deprecation") protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } String presentedPassword = authentication.getCredentials().toString(); if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } }
动态权限管理
1. DynamicSecurityService
获取所有路径对应的资源并存放到map中
修改或者删除后台资源以后需要清空动态资源重新获取
2. OncePerRequestFilter->doFilterInternal
JWT登录授权过滤器
通过UserDetailsService获取UserDetails并设置setAuthentication(UsernamePasswordAuthenticationToken)
FilterChain.doFilter
ExceptionTranslationFilter->handleSpringSecurityException-handleAuthenticationException&handleAccessDeniedException
3. AbstractSecurityInterceptor&FIlter->beforeInvocation,doFilter
doFilter中调用beforeInvocation
beforeInvocation调用getAttributes,attemptAuthorization
attemptAuthorization 调用decide
4. AuthenticationEntryPoint->commence
5. AccessDeniedHandler->handle
6. FilterInvocationSecurityMetadataSource->getAttributes
如果配置的资源map为空需要重新获取
获取访问该路径所需资源
7. AccessDecisionManager->decide
将访问所需资源和用户拥有资源进行比对
通过1获取访问所需的资源
web.ignoring()和permitAll() 区别
web.ignoring(),一般配置静态资源路径 配置以后忽略所有WebSecurity相关的过滤器
permitAll() 放行 不去做验证是否可以访问(UsernamePasswordAuthenticationToken),但还是会经过过滤器