赞
踩
spring security 是企业应用系统的权限管理框架,应用安全性包括用户认证和用户授权两部分,用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码完成认证过程,用户授权是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。spring security 的主要核心功能为认证和授权。
1.WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。
2.SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。
3.HeaderWriterFilter:用于将头信息加入响应中。
4.CsrfFilter:用于处理跨站请求伪造。
5.LogoutFilter:用于处理退出登录。
6.UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。
7.DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。
8.BasicAuthenticationFilter:检测和处理 http basic 认证。
9.RequestCacheAwareFilter:用来处理请求的缓存。
10.SecurityContextHolderAwareRequestFilter:主要是包装请求对象request。
11.AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication。
12.SessionManagementFilter:管理 session 的过滤器
13.ExceptionTranslationFilter:处理 AccessDeniedException 和
AuthenticationException 异常。
14.FilterSecurityInterceptor:可以看做过滤器链的出口。
RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
public class SnailSecurityAutoConfig extends WebSecurityConfigurerAdapter { private final CustomerSecurityProperties customerSecurityProperties; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; private final JwtAccessDeniedHandler jwtAccessDeniedHandler; private final JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter; private final CustomerUsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter; private final CustomerFilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource; private final CustomerAccessDecisionManager accessDecisionManager; public SnailSecurityAutoConfig(JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter, CustomerSecurityProperties customerSecurityProperties, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtAccessDeniedHandler jwtAccessDeniedHandler, CustomerUsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter, CustomerFilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource, CustomerAccessDecisionManager accessDecisionManager) { this.customerSecurityProperties = customerSecurityProperties; this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint; this.jwtAccessDeniedHandler = jwtAccessDeniedHandler; this.jwtAuthorizationTokenFilter = jwtAuthorizationTokenFilter; this.usernamePasswordAuthenticationFilter = usernamePasswordAuthenticationFilter; this.filterInvocationSecurityMetadataSource = filterInvocationSecurityMetadataSource; this.accessDecisionManager = accessDecisionManager; } /** * 忽略拦截url或静态资源文件夹 - web.ignoring(): 会直接过滤该url - 将不会经过Spring Security过滤器链 * http.permitAll(): 不会绕开springsecurity验证,相当于是允许该路径通过 * * @param web * @throws Exception */ @Override public void configure(WebSecurity web) { //放行swagger web.ignoring().antMatchers(HttpMethod.GET, "/v2/api-docs", "/swagger-resources", "/swagger-resources/**", "/configuration/ui", "/configuration/security", "/swagger-ui.html/**", "/webjars/**"); } @Override protected void configure(HttpSecurity http) throws Exception { ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http.authorizeRequests(); for (CustomerSecurityProperties.InterceptPath interceptPath : customerSecurityProperties.getInterceptPath()){ authorizeRequests.antMatchers(interceptPath.getUrl()).hasAnyRole(interceptPath.getRole().split("[,;]]")); } http .csrf().disable() //关闭spring自带的csrf .authorizeRequests() //验证请求 .antMatchers(customerSecurityProperties.getPublicPath()).permitAll() //配置放行url .anyRequest().authenticated() //其余需要验证 .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { object.setAccessDecisionManager(accessDecisionManager); object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource); return object; } }) //鉴权配置 .and() .logout() //登出 .logoutSuccessHandler(new CustomerLogoutSuccessHandler()) //成功登出handler .and() .exceptionHandling() .authenticationEntryPoint(jwtAuthenticationEntryPoint) //配置匿名访问无权限url 的handler .accessDeniedHandler(jwtAccessDeniedHandler) //配置登录后访问无权限url 的handler .and() .sessionManagement() //session管理 .sessionCreationPolicy(SessionCreationPolicy.STATELESS); //禁用session http.addFilterAt(usernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); //登录过滤器 http.addFilterBefore(jwtAuthorizationTokenFilter, CustomerUsernamePasswordAuthenticationFilter.class);//token过滤验证 } }
configure(WebSecurity web) 配置忽略拦截将不会经过Spring Security过滤器链,
http.permitAll(): 不会绕开springsecurity验证,相当于是允许该路径通过
@Data @Configuration @ConfigurationProperties(prefix = "snail.security") public class CustomerSecurityProperties { private Jwt jwt; private InterceptPath[] interceptPath = null; private String[] publicPath = { "/login", }; @Data public static class Jwt{ String subject = "uAuthentication"; String header = "Authorization"; String tokenStartWith = "Bearer"; String base64Secret = "jdgdjguadgliwiweiuqiahdiwuhdiahdiwuqidihshdkhiwqheiuwdwhoqdwqdjgtfghdg"; Long tokenValidityInSeconds = 7200000L; } @Data public static class InterceptPath{ private String url; /** *角色用,或者;分割 */ private String role; } }
public class CustomerLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("Application/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(ResponseVO.success()));
// authentication.setAuthenticated(false);
}
}
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@SneakyThrows
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpConstance.STATUS_DENY);
ResponseUtil.writeToResponse(response, JSON.toJSONString(ResponseVO.error(HttpConstance.STATUS_DENY,"禁止访问")));
}
}
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger log= LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.error("JwtAuthenticationEntryPoint{}","没有凭证");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setStatus(HttpConstance.STATUS_NO_AUTH);
PrintWriter pWriter = httpServletResponse.getWriter();
pWriter.write(JSON.toJSONString(ResponseVO.error(HttpConstance.STATUS_NO_AUTH,"没有凭证,验证失败")));
pWriter.flush();
pWriter.close();
}
}
@Slf4j @Data public class CustomerAuthenticationProvider implements AuthenticationProvider { private PasswordEncoder passwordEncoder; /** * The password used to perform * {@link PasswordEncoder#matches(CharSequence, String)} on when the user is * not found to avoid SEC-2056. This is necessary, because some * {@link PasswordEncoder} implementations will short circuit if the password is not * in a valid format. */ private volatile String userNotFoundEncodedPassword; private UserDetailsService userDetailsService; private UserDetailsPasswordService userDetailsPasswordService; private JwtTokenUtil jwtTokenUtil; public CustomerAuthenticationProvider(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService , JwtTokenUtil jwtTokenUtil) { setJwtTokenUtil(jwtTokenUtil); setPasswordEncoder(passwordEncoder); setUserDetailsService(userDetailsService); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = (String) authentication.getPrincipal(); String password = (String) authentication.getCredentials(); NormalUser userDetails = (NormalUser) userDetailsService.loadUserByUsername(username); log.debug(String.format("username:%s,password:%s",username,password)); //验证密码 Boolean isValid = PasswordUtil.isValid(password, userDetails.getPassword(), username, getPasswordEncoder()); if (!isValid) { throw new BadCredentialsException("密码错误!"); } //前后端分离,token生成 String token = jwtTokenUtil.createToken(userDetails.getUsername(), userDetails.getAuthorities().toString()); //String token = jwtTokenUtil.generateToken(userDetails); userDetails.setToken(token); return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities()); } //支持的验证方式是用户名密码验证及其子类的验证方式 @Override public boolean supports(Class<?> authentication) { return CustomerUsernamePasswordAuthenticationFilter.class.isAssignableFrom(authentication); } }
@Slf4j public class CustomerAuthenticationManager implements AuthenticationManager { private CustomerAuthenticationProvider authenticationProvider; public CustomerAuthenticationManager(CustomerAuthenticationProvider authenticationProvider) { this.authenticationProvider = authenticationProvider; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { AuthenticationProvider authenticationProvider = getAuthenticationProvider(); Authentication result = authenticationProvider.authenticate(authentication); if(result != null)log.info("CustomerAuthenticationManager{}",result.toString()); else{ log.info("CustomerAuthenticationManager{}","Authentication failed"); } if (Objects.nonNull(result)){ return result; } throw new ProviderNotFoundException("Authentication failed"); } public CustomerAuthenticationProvider getAuthenticationProvider() { return authenticationProvider; } public void setAuthenticationProvider(CustomerAuthenticationProvider authenticationProvider) { this.authenticationProvider = authenticationProvider; } }
@Slf4j public class CustomerUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; private boolean postOnly = true; private CustomerSecurityProperties customerSecurityProperties; private JwtTokenUtil jwtTokenUtil; public CustomerUsernamePasswordAuthenticationFilter(CustomerAuthenticationManager authenticationManager, CustomerSecurityProperties customerSecurityProperties, JwtTokenUtil jwtTokenUtil) { super(new AntPathRequestMatcher("/login", "PUT")); setAuthenticationManager(authenticationManager); setCustomerSecurityProperties(customerSecurityProperties); setJwtTokenUtil(jwtTokenUtil); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equalsIgnoreCase("PUT")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } log.info("CustomerUsernamePasswordAuthenticationFilter {}", " inter attemptAuthentication"); String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); log.debug("CustomerUsernamePasswordAuthenticationFilter{}", String.format("username:%s,password:%s", username, password)); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password, null); // Allow subclasses to set the "details" property authRequest.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authRequest); return this.getAuthenticationManager().authenticate(authRequest); } @SneakyThrows @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { NormalUser userDetail = (NormalUser) authResult.getPrincipal(); Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities(); //String token = jwtTokenUtil.createToken(user.getUsername(), authorities.toString()); log.info("successfulAuthentication {}",userDetail.getToken()); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); response.setHeader("token", customerSecurityProperties.getJwt().getHeader() + userDetail.getToken()); ResponseUtil.writeToResponse(response, JSON.toJSONString(ResponseVO.success("登录成功"))); } @SneakyThrows @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { //super.unsuccessfulAuthentication(request, response, failed); String responseData = ""; if (failed instanceof AccountExpiredException) { responseData = "账号过期"; } else if (failed instanceof BadCredentialsException) { responseData = "账号或密码错误"; } else if (failed instanceof CredentialsExpiredException) { responseData = "密码过期"; } else if (failed instanceof DisabledException) { responseData = "账号不可用"; } else if (failed instanceof LockedException) { responseData = "账号被锁定"; } else if (failed instanceof InternalAuthenticationServiceException) { responseData = "用户不存在"; } else { responseData = "未知错误"; } response.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=utf-8"); ResponseUtil.writeToResponse(response, JSON.toJSONString(ResponseVO.success("登录失败" + responseData))); } /** * Enables subclasses to override the composition of the password, such as by * including additional values and a separator. * <p> * This might be used for example if a postcode/zipcode was required in addition to * the password. A delimiter such as a pipe (|) should be used to separate the * password and extended value(s). The <code>AuthenticationDao</code> will need to * generate the expected password in a corresponding manner. * </p> * * @param request so that request attributes can be retrieved * @return the password that will be presented in the <code>Authentication</code> * request token to the <code>AuthenticationManager</code> */ @Nullable protected String obtainPassword(HttpServletRequest request) { return request.getParameter(passwordParameter); } /** * Enables subclasses to override the composition of the username, such as by * including additional values and a separator. * * @param request so that request attributes can be retrieved * @return the username that will be presented in the <code>Authentication</code> * request token to the <code>AuthenticationManager</code> */ @Nullable protected String obtainUsername(HttpServletRequest request) { return request.getParameter(usernameParameter); } /** * Sets the parameter name which will be used to obtain the username from the login * request. * * @param usernameParameter the parameter name. Defaults to "username". */ public void setUsernameParameter(String usernameParameter) { Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); this.usernameParameter = usernameParameter; } /** * Sets the parameter name which will be used to obtain the password from the login * request.. * * @param passwordParameter the parameter name. Defaults to "password". */ public void setPasswordParameter(String passwordParameter) { Assert.hasText(passwordParameter, "Password parameter must not be empty or null"); this.passwordParameter = passwordParameter; } /** * Defines whether only HTTP POST requests will be allowed by this filter. If set to * true, and an authentication request is received which is not a POST request, an * exception will be raised immediately and authentication will not be attempted. The * <tt>unsuccessfulAuthentication()</tt> method will be called as if handling a failed * authentication. * <p> * Defaults to <tt>true</tt> but may be overridden by subclasses. */ public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getUsernameParameter() { return usernameParameter; } public final String getPasswordParameter() { return passwordParameter; } public AuthenticationManager getAuthenticationManager() { return super.getAuthenticationManager(); } @Autowired public void setAuthenticationManager(CustomerAuthenticationManager authenticationManager) { super.setAuthenticationManager(authenticationManager); } public CustomerSecurityProperties getCustomerSecurityProperties() { return customerSecurityProperties; } public void setCustomerSecurityProperties(CustomerSecurityProperties customerSecurityProperties) { this.customerSecurityProperties = customerSecurityProperties; } public JwtTokenUtil getJwtTokenUtil() { return jwtTokenUtil; } public void setJwtTokenUtil(JwtTokenUtil jwtTokenUtil) { this.jwtTokenUtil = jwtTokenUtil; } }
public class CustomerFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private final IUrlService iUrlService ; public CustomerFilterInvocationSecurityMetadataSource(IUrlService iUrlService) { this.iUrlService = iUrlService; } @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { Map<RequestMatcher,Collection<ConfigAttribute>> allRoleResource = new HashMap<>(); for (NormalUrl normalUrl: iUrlService.loadAll() ){ allRoleResource.put(new AntPathRequestMatcher(normalUrl.getUrl()), SecurityConfig.createList(normalUrl.getRole().split("[,;]"))); } FilterInvocation filterInvocation = (FilterInvocation)object; HttpServletRequest request = filterInvocation.getRequest(); for (Map.Entry<RequestMatcher,Collection<ConfigAttribute>> entry : allRoleResource.entrySet()){ if (entry.getKey().matches(request)){ return entry.getValue(); } } return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }
@Slf4j
public class CustomerAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator iterator = configAttributes.iterator();
if(!iterator.hasNext()){
return;
}
if(authentication == null){
throw new DeniedAccessException( “authentication 为空,没有访问权限”,“CustomerAccessDecisionManager”);
}
while (iterator.hasNext()){ ConfigAttribute configAttribute = iterator.next(); String needCode = configAttribute.getAttribute(); Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for (GrantedAuthority grantedAuthority : authorities){ log.info("AccessDecisionManager{} {}",grantedAuthority.getAuthority(),"ROLE_"+needCode); if(StringUtils.equals(grantedAuthority.getAuthority(),"ROLE_"+needCode)){ return; } } } throw new DeniedAccessException( "authentication 没有访问权限","CustomerAccessDecisionManager"); } @Override public boolean supports(ConfigAttribute attribute) { return false; } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); }
}
public class PasswordUtil { public static Boolean isValid(String password, String realPassword, String salt, PasswordEncoder passwordEncoder) { if (password == null || realPassword == null) { throw new IllegalArgumentException("密码参数错误"); } if (passwordEncoder == null || passwordEncoder instanceof NoOpPasswordEncoder) { return Objects.equals(realPassword, password); } return passwordEncoder.matches(password + salt ,realPassword); } }
public class ResponseUtil { public static void writeToResponse(HttpServletResponse response, Object message) throws IOException, NoSuchMethodException { if(response == null || message == null){ throw new OperationFailedException("空异常","httpUtil"); } PrintWriter writer = response.getWriter(); try { if(message instanceof String ){ writer.write((String)message); }else if(message instanceof Integer){ writer.write((Integer) message); }else if(message instanceof char[]){ writer.write((char[])message); }else{ writer.write(message.toString()); } writer.flush(); }catch (Exception e){ e.printStackTrace(); throw e; }finally { writer.close(); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。