-
spring security CSRF防护
2019-07-31 19:14:30从Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT和DELETE方法进行防护。 我这边是spring boot项目,在启用了@EnableWebSecurity...CSRF是指跨站请求伪造(Cross-site request forgery),是web常见的攻击之一。
从Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT和DELETE方法进行防护。
我这边是spring boot项目,在启用了@EnableWebSecurity注解后,csrf保护就自动生效了。
所以在默认配置下,即便已经登录了,页面中发起PATCH,POST,PUT和DELETE请求依然会被拒绝,并返回403,需要在请求接口的时候加入csrfToken才行。
如果你使用了freemarker之类的模板引擎或者jsp,针对表单提交,可以在表单中增加如下隐藏域:<input type = “hidden” name = “${_csrf.parameterName}” value = “${_csrf.token}” />
如果您使用的是JSON,则无法在HTTP参数中提交CSRF令牌。相反,您可以在HTTP头中提交令牌。一个典型的模式是将CSRF令牌包含在元标记中。下面显示了一个JSP示例:
<html> <head> <meta name = “_csrf” content = “${_csrf.token}” /> <!-- 默认标题名称是X-CSRF-TOKEN --> <meta name = “_csrf_header” content = “${_csrf.headerName}” /> </ head>
然后,您可以将令牌包含在所有Ajax请求中。如果您使用jQuery,可以使用以下方法完成此操作:
var token = $("meta[name='_csrf']").attr("content"); var header = $("meta[name='_csrf_header']").attr("content"); $.ajax({ url:url, type:'POST', async:false, dataType:'json', //返回的数据格式:json/xml/html/script/jsonp/text beforeSend: function(xhr) { xhr.setRequestHeader(header, token); //发送请求前将csrfToken设置到请求头中 }, success:function(data,textStatus,jqXHR){ } });
如果你不想启用CSRF保护,可以在spring security配置中取消csrf,如下:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() ... http.csrf().disable(); //取消csrf防护 } }
-
spring security——基本介绍(一)
2019-01-16 10:31:23一、spring security 简介 spring security 的核心功能主要包括: 认证 (你是谁) 授权 (你能干什么) 攻击防护 (防止伪造身份) 其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic ...一、spring security 简介
spring security 的核心功能主要包括:
- 认证 (你是谁)
- 授权 (你能干什么)
- 攻击防护 (防止伪造身份)
其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
比如,对于username password认证过滤器来说,
会检查是否是一个登录请求;
是否包含username 和 password (也就是该过滤器需要的一些认证信息) ;
如果不满足则放行给下一个。
下一个按照自身职责判定是否是自身需要的信息,basic的特征就是在请求头中有 Authorization:Basic eHh4Onh4 的信息。中间可能还有更多的认证过滤器。最后一环是 FilterSecurityInterceptor,这里会判定该请求是否能进行访问rest服务,判断的依据是 BrowserSecurityConfig中的配置,如果被拒绝了就会抛出不同的异常(根据具体的原因)。Exception Translation Filter 会捕获抛出的错误,然后根据不同的认证方式进行信息的返回提示。
注意:绿色的过滤器可以配置是否生效,其他的都不能控制。
二、入门项目
首先创建spring boot项目HelloSecurity,其pom主要依赖如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>
然后在src/main/resources/templates/目录下创建页面:
home.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Spring Security Example</title> </head> <body> <h1>Welcome!</h1> <p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p> </body> </html>
我们可以看到, 在这个简单的视图中包含了一个链接: “/hello”. 链接到了如下的页面,Thymeleaf模板如下:
hello.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Hello World!</title> </head> <body> <h1>Hello world!</h1> </body> </html>
Web应用程序基于Spring MVC。 因此,你需要配置Spring MVC并设置视图控制器来暴露这些模板。 如下是一个典型的Spring MVC配置类。在src/main/java/hello目录下(所以java都在这里):
@Configuration public class MvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/home").setViewName("home"); registry.addViewController("/").setViewName("home"); registry.addViewController("/hello").setViewName("hello"); registry.addViewController("/login").setViewName("login"); } }
addViewControllers()方法(覆盖WebMvcConfigurerAdapter中同名的方法)添加了四个视图控制器。 两个视图控制器引用名称为“home”的视图(在home.html中定义),另一个引用名为“hello”的视图(在hello.html中定义)。 第四个视图控制器引用另一个名为“login”的视图。 将在下一部分中创建该视图。此时,可以跳过来使应用程序可执行并运行应用程序,而无需登录任何内容。然后启动程序如下:
@SpringBootApplication public class Application { public static void main(String[] args) throws Throwable { SpringApplication.run(Application.class, args); } }
2、加入Spring Security
假设你希望防止未经授权的用户访问“/ hello”。 此时,如果用户点击主页上的链接,他们会看到问候语,请求被没有被拦截。 你需要添加一个障碍,使得用户在看到该页面之前登录。您可以通过在应用程序中配置Spring Security来实现。 如果Spring Security在类路径上,则Spring Boot会使用“Basic认证”来自动保护所有HTTP端点。 同时,你可以进一步自定义安全设置。首先在pom文件中引入:
<dependencies> ... <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ... </dependencies>
如下是安全配置,使得只有认证过的用户才可以访问到问候页面:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER"); } }
WebSecurityConfig类使用了@EnableWebSecurity注解 ,以启用Spring Security的Web安全支持,并提供Spring MVC集成。它还扩展了WebSecurityConfigurerAdapter,并覆盖了一些方法来设置Web安全配置的一些细节。
configure(HttpSecurity)方法定义了哪些URL路径应该被保护,哪些不应该。具体来说,“/”和“/ home”路径被配置为不需要任何身份验证。所有其他路径必须经过身份验证。
当用户成功登录时,它们将被重定向到先前请求的需要身份认证的页面。有一个由 loginPage()指定的自定义“/登录”页面,每个人都可以查看它。
对于configureGlobal(AuthenticationManagerBuilder) 方法,它将单个用户设置在内存中。该用户的用户名为“user”,密码为“password”,角色为“USER”。
现在我们需要创建登录页面。前面我们已经配置了“login”的视图控制器,因此现在只需要创建登录页面即可:
login.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Spring Security Example </title> </head> <body> <div th:if="${param.error}"> Invalid username and password. </div> <div th:if="${param.logout}"> You have been logged out. </div> <form th:action="@{/login}" method="post"> <div><label> User Name : <input type="text" name="username"/> </label></div> <div><label> Password: <input type="password" name="password"/> </label></div> <div><input type="submit" value="Sign In"/></div> </form> </body> </html>
你可以看到,这个Thymeleaf模板只是提供一个表单来获取用户名和密码,并将它们提交到“/ login”。 根据配置,Spring Security提供了一个拦截该请求并验证用户的过滤器。 如果用户未通过认证,该页面将重定向到“/ login?error”,并在页面显示相应的错误消息。 注销成功后,我们的应用程序将发送到“/ login?logout”,我们的页面显示相应的登出成功消息。最后,我们需要向用户提供一个显示当前用户名和登出的方法。 更新hello.html 向当前用户打印一句hello,并包含一个“注销”表单,如下所示:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Hello World!</title> </head> <body> <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1> <form th:action="@{/logout}" method="post"> <input type="submit" value="Sign Out"/> </form> </body> </html>
三、参数详解
1、注解 @EnableWebSecurity
在 Spring boot 应用中使用 Spring Security,用到了 @EnableWebSecurity注解,官方说明为,该注解和 @Configuration 注解一起使用, 注解 WebSecurityConfigurer 类型的类,或者利用@EnableWebSecurity 注解继承 WebSecurityConfigurerAdapter的类,这样就构成了 Spring Security 的配置。
2、抽象类 WebSecurityConfigurerAdapter
一般情况,会选择继承 WebSecurityConfigurerAdapter 类,其官方说明为:WebSecurityConfigurerAdapter 提供了一种便利的方式去创建 WebSecurityConfigurer的实例,只需要重写 WebSecurityConfigurerAdapter 的方法,即可配置拦截什么URL、设置什么权限等安全控制。
3、方法 configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http)
Demo 中重写了 WebSecurityConfigurerAdapter 的两个方法:
/** * 通过 {@link #authenticationManager()} 方法的默认实现尝试获取一个 {@link AuthenticationManager}. * 如果被复写, 应该使用{@link AuthenticationManagerBuilder} 来指定 {@link AuthenticationManager}. * * 例如, 可以使用以下配置在内存中进行注册公开内存的身份验证{@link UserDetailsService}: * * // 在内存中添加 user 和 admin 用户 * @Override * protected void configure(AuthenticationManagerBuilder auth) { * auth * .inMemoryAuthentication().withUser("user").password("password").roles("USER").and() * .withUser("admin").password("password").roles("USER", "ADMIN"); * } * * // 将 UserDetailsService 显示为 Bean * @Bean * @Override * public UserDetailsService userDetailsServiceBean() throws Exception { * return super.userDetailsServiceBean(); * } * */ protected void configure(AuthenticationManagerBuilder auth) throws Exception { this.disableLocalConfigureAuthenticationBldr = true; } /** * 复写这个方法来配置 {@link HttpSecurity}. * 通常,子类不能通过调用 super 来调用此方法,因为它可能会覆盖其配置。 默认配置为: * * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); * */ protected void configure(HttpSecurity http) throws Exception { logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic(); }
4、final 类 HttpSecurity
HttpSecurity 常用方法及说明:
方法 说明 openidLogin()
用于基于 OpenId 的验证 headers()
将安全标头添加到响应 cors()
配置跨域资源共享( CORS ) sessionManagement()
允许配置会话管理 portMapper()
允许配置一个 PortMapper
(HttpSecurity#(getSharedObject(class))
),其他提供SecurityConfigurer
的对象使用PortMapper
从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl
映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443jee()
配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理 x509()
配置基于x509的认证 rememberMe
允许配置“记住我”的验证 authorizeRequests()
允许基于使用 HttpServletRequest
限制访问requestCache()
允许配置请求缓存 exceptionHandling()
允许配置错误处理 securityContext()
在 HttpServletRequests
之间的SecurityContextHolder
上设置SecurityContext
的管理。 当使用WebSecurityConfigurerAdapter
时,这将自动应用servletApi()
将 HttpServletRequest
方法与在其上找到的值集成到SecurityContext
中。 当使用WebSecurityConfigurerAdapter
时,这将自动应用csrf()
添加 CSRF 支持,使用 WebSecurityConfigurerAdapter
时,默认启用logout()
添加退出登录支持。当使用 WebSecurityConfigurerAdapter
时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()
身份验证,清除SecurityContextHolder
,然后重定向到”/login?success”anonymous()
允许配置匿名用户的表示方法。 当与 WebSecurityConfigurerAdapter
结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken
表示,并包含角色 “ROLE_ANONYMOUS”formLogin()
指定支持基于表单的身份验证。如果未指定 FormLoginConfigurer#loginPage(String)
,则将生成默认登录页面oauth2Login()
根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证 requiresChannel()
配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射 httpBasic()
配置 Http Basic 验证 addFilterAt()
在指定的Filter类的位置添加过滤器 5、类 AuthenticationManagerBuilder
/** * {@link SecurityBuilder} used to create an {@link AuthenticationManager}. Allows for * easily building in memory authentication, LDAP authentication, JDBC based * authentication, adding {@link UserDetailsService}, and adding * {@link AuthenticationProvider}'s. */
意思是,AuthenticationManagerBuilder 用于创建一个 AuthenticationManager,让我能够轻松的实现内存验证、LADP验证、基于JDBC的验证、添加UserDetailsService、添加AuthenticationProvider。
四、原理讲解
1、校验流程图
2、源码分析
- AbstractAuthenticationProcessingFilter 抽象类
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } if (logger.isDebugEnabled()) { logger.debug("Request is to process authentication"); } Authentication authResult; try { authResult = attemptAuthentication(request, response); if (authResult == null) { // return immediately as subclass has indicated that it hasn't completed // authentication return; } sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user.", failed); unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { // Authentication failed unsuccessfulAuthentication(request, response, failed); return; } // Authentication success if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authResult); }
调用 requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。如果需要验证,则会调用 attemptAuthentication(HttpServletRequest, HttpServletResponse) 方法,有三种结果:
- 返回一个 Authentication 对象。配置的 SessionAuthenticationStrategy` 将被调用,然后 然后调用 successfulAuthentication(HttpServletRequest,HttpServletResponse,FilterChain,Authentication) 方法。
- 验证时发生 AuthenticationException。unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException) 方法将被调用。
- 返回Null,表示身份验证不完整。假设子类做了一些必要的工作(如重定向)来继续处理验证,方法将立即返回。假设后一个请求将被这种方法接收,其中返回的Authentication对象不为空。
- UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter的子类)
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
attemptAuthentication () 方法将 request 中的 username 和 password 生成 UsernamePasswordAuthenticationToken 对象,用于 AuthenticationManager 的验证(即 this.getAuthenticationManager().authenticate(authRequest) )。默认情况下注入 Spring 容器的 AuthenticationManager 是 ProviderManager。
- ProviderManager(AuthenticationManager的实现类)
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } } catch (AccountStatusException e) { prepareException(e, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to // invalid account status throw e; } catch (InternalAuthenticationServiceException e) { prepareException(e, authentication); throw e; } catch (AuthenticationException e) { lastException = e; } } if (result == null && parent != null) { // Allow the parent to try. try { result = parent.authenticate(authentication); } catch (ProviderNotFoundException e) { // ignore as we will throw below if no other exception occurred prior to // calling parent and the parent // may throw ProviderNotFound even though a provider in the child already // handled the request } catch (AuthenticationException e) { lastException = e; } } if (result != null) { if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) { // Authentication is complete. Remove credentials and other secret data // from authentication ((CredentialsContainer) result).eraseCredentials(); } eventPublisher.publishAuthenticationSuccess(result); return result; } // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null) { lastException = new ProviderNotFoundException(messages.getMessage( "ProviderManager.providerNotFound", new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}")); } prepareException(lastException, authentication); throw lastException; }
尝试验证 Authentication 对象。AuthenticationProvider 列表将被连续尝试,直到 AuthenticationProvider 表示它能够认证传递的过来的Authentication 对象。然后将使用该 AuthenticationProvider 尝试身份验证。如果有多个 AuthenticationProvider 支持验证传递过来的Authentication 对象,那么由第一个来确定结果,覆盖早期支持AuthenticationProviders 所引发的任何可能的AuthenticationException。 成功验证后,将不会尝试后续的AuthenticationProvider。如果最后所有的 AuthenticationProviders 都没有成功验证 Authentication 对象,将抛出 AuthenticationException。从代码中不难看出,由 provider 来验证 authentication, 核心点方法是:
Authentication result = provider.authenticate(authentication);
此处的 provider 是 AbstractUserDetailsAuthenticationProvider,AbstractUserDetailsAuthenticationProvider 是AuthenticationProvider的实现,看看它的 authenticate(authentication) 方法:
// 验证 authentication public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage( "AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // Determine username String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { logger.debug("User '" + username + "' not found"); if (hideUserNotFoundExceptions) { throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else { throw notFound; } } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } try { preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { if (cacheWasUsed) { // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed = false; user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } else { throw exception; } } postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (forcePrincipalAsString) { principalToReturn = user.getUsername(); } return createSuccessAuthentication(principalToReturn, authentication, user); }
AbstractUserDetailsAuthenticationProvider 内置了缓存机制,从缓存中获取不到的 UserDetails 信息的话,就调用如下方法获取用户信息,然后和 用户传来的信息进行对比来判断是否验证成功。
// 获取用户信息 UserDetails user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
retrieveUser() 方法在 DaoAuthenticationProvider 中实现,DaoAuthenticationProvider 是 AbstractUserDetailsAuthenticationProvider的子类。具体实现如下:
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; try { loadedUser = this.getUserDetailsService().loadUserByUsername(username); } catch (UsernameNotFoundException notFound) { if (authentication.getCredentials() != null) { String presentedPassword = authentication.getCredentials().toString(); passwordEncoder.isPasswordValid(userNotFoundEncodedPassword, presentedPassword, null); } throw notFound; } catch (Exception repositoryProblem) { throw new InternalAuthenticationServiceException( repositoryProblem.getMessage(), repositoryProblem); } if (loadedUser == null) { throw new InternalAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; }
可以看到此处的返回对象 userDetails 是由 UserDetailsService 的 #loadUserByUsername(username) 来获取的。
对于看本文比较困难的同学可以移步:https://www.lanqiao.cn/courses/3013
邀请码:STyLDQzM
其实内容和博客差不多,只不过更详细(就是一步一步的来,所有步骤有详细过程记录和截图,按照课程步骤最终能完整的操作完整个项目过程,适合小白),版本上也有所升级,还有就是有问题可以直接沟通,谢谢支持!
-
Spring Security
2019-09-18 18:06:00Spring SecuritySpring Security 权限拦截
Spring Security 数据库管理、权限缓存、自定义决策
package org.springframework.security.core.userdetails; public interface UserDetailsService { /** * Locates the user based on the username. In the actual implementation, the search * may possibly be case sensitive, or case insensitive depending on how the * implementation instance is configured. In this case, the <code>UserDetails</code> * object that comes back may have a username that is of a different case than what * was actually requested.. * @param username the username identifying the user whose data is required. * @return a fully populated user record (never <code>null</code>) * @throws UsernameNotFoundException if the user could not be found or the user has no * GrantedAuthority */ //根据用户名来获取用户信息 UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
package org.springframework.security.core.userdetails; //这里省略了Spring的洼释,如果想了解Spring对这些方法的注释,请查看Spring源码 public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities();//权限集合 String getPassword();//密码 String getUsername();//用户名 boolean isAccountNonExpired();//账户没有过期 boolean isAccountNonLocked();//账户没有被锁定 boolean isCredentialsNonExpired();//证书没有过期 boolean isEnabled();//账户是否有效 }
package org.springframework.security.core; public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities();//权限集合 Object getCredentials();//获取凭证 Object getDetails();//获取认证一些额外信息 Object getPrincipal();//过去认证的实体 boolean isAuthenticated();//是否认证通过 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }
package org.springframework.security.config.authentication; public class CachingUserDetailsService implements UserDetailsService { private UserCache userCache = new NullUserCache(); private final UserDetailsService delegate; CachingUserDetailsService(UserDetailsService delegate) { this.delegate = delegate; } public UserCache getUserCache() { return this.userCache; } public void setUserCache(UserCache userCache) { this.userCache = userCache; } public UserDetails loadUserByUsername(String username) { UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { user = this.delegate.loadUserByUsername(username); } Assert.notNull(user, () -> { return "UserDetailsService " + this.delegate + " returned null for username " + username + ". This is an interface contract violation"; }); this.userCache.putUserInCache(user); return user; } }
package org.springframework.security.core.userdetails.cache; public class EhCacheBasedUserCache implements UserCache, InitializingBean { private static final Log logger = LogFactory.getLog(EhCacheBasedUserCache.class); private Ehcache cache; public void afterPropertiesSet() throws Exception { Assert.notNull(cache, "cache mandatory"); } public Ehcache getCache() { return cache; } public UserDetails getUserFromCache(String username) { Element element = cache.get(username); if (logger.isDebugEnabled()) { logger.debug("Cache hit: " + (element != null) + "; username: " + username); } if (element == null) { return null; } else { return (UserDetails) element.getValue(); } } public void putUserInCache(UserDetails user) { Element element = new Element(user.getUsername(), user); if (logger.isDebugEnabled()) { logger.debug("Cache put: " + element.getKey()); } cache.put(element); } public void removeUserFromCache(UserDetails user) { if (logger.isDebugEnabled()) { logger.debug("Cache remove: " + user.getUsername()); } this.removeUserFromCache(user.getUsername()); } public void removeUserFromCache(String username) { cache.remove(username); } public void setCache(Ehcache cache) { this.cache = cache; } }
package org.springframework.security.access.vote; public abstract class AbstractAccessDecisionManager implements AccessDecisionManager, InitializingBean, MessageSourceAware { protected final Log logger = LogFactory.getLog(getClass()); private List<AccessDecisionVoter<? extends Object>> decisionVoters; protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); private boolean allowIfAllAbstainDecisions = false; protected AbstractAccessDecisionManager( List<AccessDecisionVoter<? extends Object>> decisionVoters) { Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required"); this.decisionVoters = decisionVoters; } public void afterPropertiesSet() throws Exception { Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required"); Assert.notNull(this.messages, "A message source must be set"); } protected final void checkAllowIfAllAbstainDecisions() { if (!this.isAllowIfAllAbstainDecisions()) { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); } } public List<AccessDecisionVoter<? extends Object>> getDecisionVoters() { return this.decisionVoters; } public boolean isAllowIfAllAbstainDecisions() { return allowIfAllAbstainDecisions; } public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) { this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions; } public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } public boolean supports(ConfigAttribute attribute) { for (AccessDecisionVoter voter : this.decisionVoters) { if (voter.supports(attribute)) { return true; } } return false; } public boolean supports(Class<?> clazz) { for (AccessDecisionVoter voter : this.decisionVoters) { if (!voter.supports(clazz)) { return false; } } return true; } }
package org.springframework.security.access; public interface AccessDecisionVoter<S> { int ACCESS_GRANTED = 1; int ACCESS_ABSTAIN = 0; int ACCESS_DENIED = -1; boolean supports(ConfigAttribute attribute); boolean supports(Class<?> clazz); int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes); }
package org.springframework.security.access.vote; public class RoleVoter implements AccessDecisionVoter<Object> { private String rolePrefix = "ROLE_"; public String getRolePrefix() { return rolePrefix; } public void setRolePrefix(String rolePrefix) { this.rolePrefix = rolePrefix; } public boolean supports(ConfigAttribute attribute) { if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix())) { return true; } else { return false; } } public boolean supports(Class<?> clazz) { return true; } public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { if (authentication == null) { return ACCESS_DENIED; } int result = ACCESS_ABSTAIN; Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication); for (ConfigAttribute attribute : attributes) { if (this.supports(attribute)) { result = ACCESS_DENIED; // Attempt to find a matching granted authority for (GrantedAuthority authority : authorities) { if (attribute.getAttribute().equals(authority.getAuthority())) { return ACCESS_GRANTED; } } } } return result; } Collection<? extends GrantedAuthority> extractAuthorities( Authentication authentication) { return authentication.getAuthorities(); } }
package org.springframework.security.access.vote; public class AffirmativeBased extends AbstractAccessDecisionManager { public AffirmativeBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) { super(decisionVoters); } public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: return; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); } }
package org.springframework.security.access.vote; public class ConsensusBased extends AbstractAccessDecisionManager { private boolean allowIfEqualGrantedDeniedDecisions = true; public ConsensusBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) { super(decisionVoters); } public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int grant = 0; int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: grant++; break; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (grant > deny) { return; } if (deny > grant) { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); } if ((grant == deny) && (grant != 0)) { if (this.allowIfEqualGrantedDeniedDecisions) { return; } else { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); } } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); } public boolean isAllowIfEqualGrantedDeniedDecisions() { return allowIfEqualGrantedDeniedDecisions; } public void setAllowIfEqualGrantedDeniedDecisions( boolean allowIfEqualGrantedDeniedDecisions) { this.allowIfEqualGrantedDeniedDecisions = allowIfEqualGrantedDeniedDecisions; } }
package org.springframework.security.access.vote; public class UnanimousBased extends AbstractAccessDecisionManager { public UnanimousBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) { super(decisionVoters); } public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) throws AccessDeniedException { int grant = 0; List<ConfigAttribute> singleAttributeList = new ArrayList<>(1); singleAttributeList.add(null); for (ConfigAttribute attribute : attributes) { singleAttributeList.set(0, attribute); for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, singleAttributeList); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: grant++; break; case AccessDecisionVoter.ACCESS_DENIED: throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); default: break; } } } // To get this far, there were no deny votes if (grant > 0) { return; } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); } }
-
SpringSecurity
2020-12-13 21:56:20SpringSecurity(spring安全)SpringSecurity是用来干嘛的?Spring Security 核心功能Spring Security 工作流程1.导入POM坐标,thymeleaf用来测试Restful,security2.访问资源3.AOP横切进入,不用繁琐配置,来一个...SpringSecurity(spring安全)
设计之初就需要考虑,网站安全 比如SpringSecurity和shiro, 认证、授权(vip1,vip2,vip3)
SpringSecurity是用来干嘛的?
其实spring Security就是一个过滤器链,而最核心的是 Basic Authentication Filter ,会认证用户身份。
Spring Security 核心功能
· 认证 (你是谁)
· 授权 (你能干什么)
· 攻击防护 (防止伪造身份)Spring Security 工作流程
图来源于此博客
其中绿色部分的每一种过滤器代表着一种认证方式,主要工作检查当前请求有没有关于用户信息,如果当前的没有,就会跳入到下一个绿色的过滤器中,请求成功会打标记。绿色认证方式可以配置,比如短信认证,微信。比如如果我们不配置 BasicAuthenticationFilter 的话,那么它就不会生效。FilterSecurityInterceptor 过滤器是最后一个,它会决定当前的请求可不可以访问Controller,判断规则放在这个里面。当不通过时会把异常抛给在这个过滤器的前面的 ExceptionTranslationFilter 过滤器。
ExceptionTranslationFilter 接收到异常信息时,将跳转页面引导用户进行认证。橘黄色和蓝色的位置不可更改。当没有认证的request进入过滤器链时,首先进入到 FilterSecurityInterceptor,判断当前是否进行了认证,如果没有认证则进入到 ExceptionTranslationFilter,进行抛出异常,然后跳转到认证页面(登录界面)。
1.导入POM坐标,thymeleaf用来测试Restful,security
<!-- spring-boot-starter-thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- spring-boot-starter-security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
坐标一旦配置 就会自动拦截
引入了 spring Security 如果什么都不设置自动会卡在第一关登录 登录名默认为 user 密码 会在控制台显示
2.访问资源
导入静态资源和html页面
随便写个 controller
运行
已经被拦截了。3.AOP横切进入,不用繁琐配置,来一个配置类
记住几个 类 1. WebSecurityConfigurerAdapter :(适配器模式,自定义Security策略) 2. AuthenticationManagerBuilder : (建造者模式,自定义认证策略) 3. @EnableWebSecurity :开启WebSecurity模式, @EnableXXX 开启某个功能
-
springSecurity
2019-05-19 18:25:41什么是springSecurity springSecurity是spring组织推出的一款安全框架. springSecurity的作用: a. 用户认证(就是判断用户有没有登录)(不够专业, 做用户登录更专业) b. 权限管理(用户登录过后, 判断用户是具体... -
SpringSecurity与JWT整合
2020-12-06 21:14:33SpringSecurity与JWT整合 数据库基于:SpringSecurity从数据库中获取用户信息进行验证 依赖: <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId... -
在Spring Boot中使用Spring Security实现权限控制
2017-01-12 15:52:37Spring Boot框架我们前面已经介绍了很多了,相信看了前面的博客的小伙伴对Spring Boot...OK,那我们今天要说的是Spring Boot中另外一个比较重要的东西,那就是Spring Security,这是一个专门针对基于Spring的项目的安全 -
SpringSecurity从数据库中获取用户信息进行验证
2020-12-05 22:17:23SpringSecurity从数据库中获取用户信息进行验证 基于 SpringBoot与SpringSecurity整合 案例的修改: 数据库 user 表 注,密码是由 BCrypt 算法加密对应用户名所得。 root $2a$10$uzHVooZlCWBkaGScKnpha.ZrK31NI... -
SpringBoot集成Spring Security(1)——入门程序
2018-05-09 09:47:20因为项目需要,第一次接触Spring Security,早就听闻Spring Security强大但上手困难,今天学习了一天,翻遍了全网资料,才仅仅出入门道,特整理这篇文章来让后来者少踩一点坑(本文附带实例程序,请放心食用) ... -
-
SpringSecurity入门到入土教程_1
2020-12-30 21:45:59文章目录SpringSecurity 教程1. 简介1.1 概念1.2 入门案例1.3 自定义登录逻辑1.4 自定义登录页面1.5 自定用户名参数1.5 自定义成功处理器1.6 登录失败处理器1.7 认证anyRequestantMatchersregexMatchers1.8 授权基于... -
Spring Security 工作原理概览
2019-04-27 08:02:58本文由读者 muggle 投稿,muggle 是一位具备极客精神的90后单身老实猿,对 Spring Security 有丰富的使用经验,muggle 个人博客地址是 h... -
Springboot集成SpringSecurity 附代码
2018-02-12 17:37:06Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。 它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversi... -
spring security
2011-12-17 16:24:18xmlns:s="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans ... -
Spring Security 和 SpringCloud Security 有什么区别?
2020-11-12 16:20:55Spring Security 和 SpringCloud Security 有什么区别? -
SpringSecurity权限管理系统实战—四、整合SpringSecurity(上)
2020-07-20 12:14:52自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了 自动化配置方案,可以零配置使用 Spring Security。 其实Spring Security 最早不叫 Spring Security ,叫 Acegi Security,后来才发展成为... -
spring boot + spring security 多module场景下 spring security 不起作用
2019-02-20 15:11:21spring boot + spring security 多module场景下 spring security 不起作用 问题场景描述 security-demo负责项目的整个安全模块,和安全有关的都在这个模块里 app-demo 是项目的运行模块 按照这个准备进行项目搭建,... -
spring security logout(spring security登出示例)
2016-12-01 14:23:03**spring security logout(spring security登出示例)** 在学习实现spring security登出的时候发现了一篇外文,感觉写的挺好,这里斗胆尝试翻译出来,原文链接:... -
SpringSecurity、Spring Social、SpringSecurity OAuth的关系
2019-03-12 23:33:40SpringSecurity是核心; Spring Social是一个社交项目,作用是连接到社交网站上去, 如在网站登录的时候,我们经常可以看到使用第三方账号登录(微信账号、QQ账号等) SpringSecurity OAuth,上述的三种方式都是基于... -
[Spring Security] Spring Security OAuth2(密码模式)
2019-06-02 19:49:53目录[Spring Security] Spring Security OAuth2(密码模式)简介名词定义准备工作OAuth2流程核心配置类总结REFRENCES更多 手机用户请横屏获取最佳阅读体验,REFERENCES中是本文参考的链接,如需要链接和更多资源,... -
基于Spring Security实现权限管理系统
2018-11-06 16:49:49基于Spring Security实现权限管理系统 稍微复杂一点的后台系统都会涉及到用户权限管理。何谓用户权限?我的理解就是,权限就是对数据(系统的实体类)和数据可进行的操作(增删查改)的集中管理。要构建一个可用的... -
JWT教程_2 SpringSecurity与JWT整合
2021-01-02 16:03:123. SpringSecurity与JWT整合 依赖: <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> &... -
spring security中@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter四者的区别
2018-07-01 12:19:59spring security中可以通过表达式控制方法权限: Spring Security中定义了四个支持使用表达式的注解,分别是@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。其中前两者可以用来在方法调用前或者调用后... -
Springboot + Spring Security 实现前后端分离登录认证及权限控制
2019-09-05 16:53:37Springboot + Spring Security 实现前后端分离登录认证及权限控制前言本文主要的功能文章目录一、数据库表设计建表语句初始化表数据语句二、Spring Security核心配置:WebSecurityConfig三、用户登录认证逻辑:... -
Spring Security学习路径(含认证、授权、OAuth2.0与Reactive Spring Security)
2020-08-10 13:42:52Spring Boot 2.x实战77 - Spring Security 1 - Spring Boot下的Spring Security(自动配置)与Web安全配置 Spring Boot 2.x实战78 - Spring Security 2 - Spring Security的认证(Authentication) Spring Boot 2.x... -
Spring Security 实战干货:Spring Boot 中的 Spring Security 自动配置初探
2019-10-14 17:36:121. 前言 ...我们发现 Spring Security Starter相关的 Servlet 自动配置都在spring-boot-autoconfigure-2.1.9.RELEASE(当前 Spring Boot 版本为2.1.9.RELEASE) 模块的路径org.springframework.boot.... -
Spring Security(一):最简单的Spring Security程序
2019-05-21 11:26:27一:最简单的Spring Security程序 1. 引入spring-boot-starter-security依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</... -
Spring Security 实战干货:图解Spring Security的过滤器体系
2020-07-02 07:50:00欢迎加入[微信圈子]程序员交流圈交流编程经验。1. 前言我在Spring Security 实战干货:内置 Filter 全解析对Spring Security的内置过滤器进行罗列...