博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring security之httpSecurity 专题
阅读量:6983 次
发布时间:2019-06-27

本文共 45265 字,大约阅读时间需要 150 分钟。

 

37.5.2 Resolving the CsrfToken

Spring Security provides CsrfTokenArgumentResolver which can automatically resolve the current CsrfToken for Spring MVC arguments. By using  you will automatically have this added to your Spring MVC configuration. If you use XML based configuraiton, you must add this yourself.

Once CsrfTokenArgumentResolver is properly configured, you can expose the CsrfToken to your static HTML based application.

@RestControllerpublic class CsrfController {    @RequestMapping("/csrf")    public CsrfToken csrf(CsrfToken token) {        return token;    }}

 

It is important to keep the CsrfToken a secret from other domains. This means if you are using , you should NOT expose the CsrfTokento any external domains.

https://docs.spring.io/spring-security/site/docs/4.2.3.RELEASE/reference/htmlsingle/#mvc-csrf

Include CSRF token in action

If allowing unauthorized users to upload temporariy files is not acceptable, an alternative is to place the MultipartFilter after the Spring Security filter and include the CSRF as a query parameter in the action attribute of the form. An example with a jsp is shown below

The disadvantage to this approach is that query parameters can be leaked. More genearlly, it is considered best practice to place sensitive data within the body or headers to ensure it is not leaked.

Additional information can be found in .

https://docs.spring.io/spring-security/site/docs/4.2.3.RELEASE/reference/htmlsingle/#csrf-include-csrf-token-in-action

 

 

 

 

如果启动csrf,Spring Security  /login和/logout的form中会自动集成csrf 

 

运行时的html:
源代码:

 

 解决下面报错的方案:

https://github.com/spring-projects/spring-security/issues/3906
As you suggested, the only thing you can really do to prevent this is to eagerly create the CsrfToken. In Spring Security 4.1.0.RELEASE+ and Java Configuration you can do this with:

http        .csrf()         .csrfTokenRepository(new HttpSessionCsrfTokenRepository())

This tells Spring Security that you do not want the token to be lazily persisted. If you are concerned with creating a session, you can also use the cookie implementation. For example:

http    .csrf()        .csrfTokenRepository(new CookieCsrfTokenRepository())

With Spring Boot, you can override the Spring Security version using the information from :

build.gradle

ext['spring-security.version'] = '4.1.0.RELEASE'

pom.xml

4.1.0.RELEASE

 

 

with pages that do not require a login attempt to add a CSRF token, which is "too late" because some of the response has already been committed.Suggested resolution: when using extras-springsecurity, (conditionally?) force generation of the session before starting to write to the buffer. (This is easily achievable using a Filter outside Thymeleaf but is somewhat tricky to identify)Caused by: java.lang.IllegalStateException: Cannot create a session after the response has been committedat org.apache.catalina.connector.Request.doGetSession(Request.java:2886)at org.apache.catalina.connector.Request.getSession(Request.java:2256)at org.apache.catalina.connector.RequestFacade.getSession(RequestFacade.java:895)at org.apache.catalina.connector.RequestFacade.getSession(RequestFacade.java:907)at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)at org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.saveToken(HttpSessionCsrfTokenRepository.java:63)at org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken.saveTokenIfNecessary(LazyCsrfTokenRepository.java:176)at org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken.getToken(LazyCsrfTokenRepository.java:128)at org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor.getExtraHiddenFields(CsrfRequestDataValueProcessor.java:71)at org.thymeleaf.spring4.requestdata.RequestDataValueProcessor4Delegate.getExtraHiddenFields(RequestDataValueProcessor4Delegate.java:86)at org.thymeleaf.spring4.requestdata.RequestDataValueProcessorUtils.getExtraHiddenFields(RequestDataValueProcessorUtils.java:155)at org.thymeleaf.spring4.processor.SpringActionTagProcessor.doProcess(SpringActionTagProcessor.java:116)at org.thymeleaf.standard.processor.AbstractStandardExpressionAttributeTagProcessor.doProcess(AbstractStandardExpressionAttributeTagProcessor.java:98)at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74)

 

 

 

 

To configure the custom login form, create a @Configuration class by extending the WebSecurityConfigurerAdapter class as follows.

import org.springframework.security.authentication.BadCredentialsException;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.GrantedAuthority;@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {   @Override   protected void configure(AuthenticationManagerBuilder auth) throws Exception {      auth.inMemoryAuthentication()      .withUser("admin").password("admin123").roles("USER");   }   @Override   protected void configure(HttpSecurity http) throws Exception {      http.authorizeRequests().anyRequest().hasAnyRole("USER","ADMIN")      .and()      .authorizeRequests().antMatchers("/login**").permitAll()      .and()      .formLogin()      .loginPage("/login") // Specifies the login page URL      .loginProcessingUrl("/signin") // Specifies the URL,which is used                                      //in action attribute on the 
tag .usernameParameter("userid") // Username parameter, used in name attribute // of the
tag. Default is 'username'. .passwordParameter("passwd") // Password parameter, used in name attribute // of the
tag. Default is 'password'. .successHandler((req,res,auth)->{ //Success handler invoked after successful authentication for (GrantedAuthority authority : auth.getAuthorities()) { System.out.println(authority.getAuthority()); } System.out.println(auth.getName()); res.sendRedirect("/"); // Redirect user to index/home page })// .defaultSuccessUrl("/") // URL, where user will go after authenticating successfully. // Skipped if successHandler() is used. .failureHandler((req,res,exp)->{ // Failure handler invoked after authentication failure String errMsg=""; if(exp.getClass().isAssignableFrom(BadCredentialsException.class)){ errMsg="Invalid username or password."; }else{ errMsg="Unknown error - "+exp.getMessage(); } req.getSession().setAttribute("message", errMsg); res.sendRedirect("/login"); // Redirect user to login page with error message. })// .failureUrl("/login?error") // URL, where user will go after authentication failure. // Skipped if failureHandler() is used. .permitAll() // Allow access to any URL associate to formLogin() .and() .logout() .logoutUrl("/signout") // Specifies the logout URL, default URL is '/logout' .logoutSuccessHandler((req,res,auth)->{ // Logout handler called after successful logout req.getSession().setAttribute("message", "You are logged out successfully."); res.sendRedirect("/login"); // Redirect user to login page with message. })// .logoutSuccessUrl("/login") // URL, where user will be redirect after successful // logout. Ignored if logoutSuccessHandler() is used. .permitAll() // Allow access to any URL associate to logout() .and() .csrf().disable(); // Disable CSRF support }}

https://www.boraji.com/spring-security-4-custom-login-from-example

 

 

 

 

org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer

public final class RememberMeConfigurer
> extends AbstractHttpConfigurer
, H> { /** * The default name for remember me parameter name and remember me cookie name */ private static final String DEFAULT_REMEMBER_ME_NAME = "remember-me";

 

 

 

使用Spring-security默认的Form登陆页:

import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    @Override    protected void configure(HttpSecurity http) throws Exception {        http.formLogin()                .successForwardUrl("/home")                .and()                .authorizeRequests().anyRequest().permitAll();    }}

 

import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class HomeController {    @RequestMapping("/home")    public ResponseEntity
home() { return ResponseEntity.ok("success"); }}

 访问http://localhost:8080/home会跳转到 http://localhost:8080/login输入用户名和密码,则会跳转到 http://localhost:8080/home

 

 

如果在HttpSecurity中配置需要authenticate(),则如果没有登陆,或没有相关权限,则会无法访问

2017-01-02 23:39:32.027 DEBUG 10396 --- [nio-8080-exec-8] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/user'; against '/auth/**'2017-01-02 23:39:32.028 DEBUG 10396 --- [nio-8080-exec-8] o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /user; Attributes: [authenticated]2017-01-02 23:39:32.029 DEBUG 10396 --- [nio-8080-exec-8] o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS2017-01-02 23:39:32.030 DEBUG 10396 --- [nio-8080-exec-8] o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@292b8b5a, returned: -12017-01-02 23:39:32.034 DEBUG 10396 --- [nio-8080-exec-8] o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is anonymous); redirecting to authentication entry pointorg.springframework.security.access.AccessDeniedException: Access is denied    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:124) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) [spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) [spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169) [spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEAS

 

 

配置session管理

下面的配置展示了只允许认证用户在同一时间只有一个实例是如何配置的。若一个用户使用用户名为"user"认证并且没有退出,同一个名为“user”的试图再次认证时,第一个用户的session将会强制销毁,并设置到"/login?expired"的url。

@Configuration      @EnableWebSecurity      public class SessionManagementSecurityConfig extends              WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .anyRequest().hasRole("USER")                      .and()                 .formLogin()                      .permitAll()                      .and()                 .sessionManagement()                      .maximumSessions(1)                      .expiredUrl("/login?expired");          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth.                  inMemoryAuthentication()                      .withUser("user")                          .password("password")                          .roles("USER");          }      }

当使用SessionManagementConfigurer的maximumSessio(int)时不用忘记为应用配置HttpSessionEventPublisher,这样能保证过期的session能够被清除。

在web.xml中可以这样配置:

org.springframework.security.web.session.HttpSessionEventPublisher

 

 

httpSecurity

   类似于spring security的xml配置文件命名空间配置中的<http>元素。它允许对特定的http请求基于安全考虑进行配置。默认情况下,适用于所有的请求,但可以使用requestMatcher(RequestMatcher)或者其它相似的方法进行限制。

使用示例:

最基本的基于表单的配置如下。该配置将所有的url访问权限设定为角色名称为"ROLE_USER".同时也定义了内存认证模式:使用用户名"user"和密码“password”,角色"ROLE_USER"来认证。

@Configuration  @EnableWebSecurity  public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {       @Override      protected void configure(HttpSecurity http) throws Exception {          http              .authorizeRequests()                  .antMatchers("/").hasRole("USER")                  .and()              .formLogin();      }       @Override      protected void configure(AuthenticationManagerBuilder auth) throws Exception {          auth               .inMemoryAuthentication()                    .withUser("user")                         .password("password")                         .roles("USER");      }  }

 配置基于openId的认证方式

 basic示例,不使用attribute exchange

@Configuration      @EnableWebSecurity      public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) {              http                  .authorizeRequests()                      .antMatchers("/").hasRole("USER")                      .and()                  .openidLogin()                      .permitAll();          }               @Override          protected void configure(AuthenticationManagerBuilder auth) throws Exception {              auth                      .inMemoryAuthentication()                          // the username must match the OpenID of the user you are                          // logging in with                          .withUser("https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU")                              .password("password")                              .roles("USER");          }      }

下面展示一个更高级的示例,使用attribute exchange

@Configuration      @EnableWebSecurity      public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) {              http                  .authorizeRequests()                      .antMatchers("/").hasRole("USER")                      .and()                  .openidLogin()                      .loginPage("/login")                      .permitAll()                      .authenticationUserDetailsService(new AutoProvisioningUserDetailsService())                          .attributeExchange("https://www.google.com/.")                              .attribute("email")                                  .type("http://axschema.org/contact/email")                                  .required(true)                                  .and()                              .attribute("firstname")                                  .type("http://axschema.org/namePerson/first")                                  .required(true)                                  .and()                              .attribute("lastname")                                  .type("http://axschema.org/namePerson/last")                                  .required(true)                                  .and()                              .and()                          .attributeExchange(".yahoo.com.")                              .attribute("email")                                  .type("http://schema.openid.net/contact/email")                                  .required(true)                                  .and()                              .attribute("fullname")                                  .type("http://axschema.org/namePerson")                                  .required(true)                                  .and()                              .and()                          .attributeExchange(".myopenid.com.")                              .attribute("email")                                  .type("http://schema.openid.net/contact/email")                                  .required(true)                                  .and()                              .attribute("fullname")                                  .type("http://schema.openid.net/namePerson")                                  .required(true);          }      }           public class AutoProvisioningUserDetailsService implements              AuthenticationUserDetailsService<OpenIDAuthenticationToken> {          public UserDetails loadUserDetails(OpenIDAuthenticationToken token) throws UsernameNotFoundException {              return new User(token.getName(), "NOTUSED", AuthorityUtils.createAuthorityList("ROLE_USER"));          }      }

增加响应安全报文头

默认情况下当使用WebSecuirtyConfigAdapter的默认构造函数时激活。

仅触发Headers()方法而不触发其它方法或者接受WebSecurityConfigureerAdater默认的,等同于:

@Configuration      @EnableWebSecurity       public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {          @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .headers()                      .contentTypeOptions();                      .xssProtection()                      .cacheControl()                      .httpStrictTransportSecurity()                      .frameOptions()                      .and()                  ...;          }      }

 

项目中用到iframe嵌入网页,然后用到springsecurity就被拦截了 浏览器报错"Refused to display 'http://......' in a frame because it set 'X-Frame-Options' to 'DENY'. "错误

原因是因为springSecurty使用X-Frame-Options防止网页被Frame
解决办法:

@Override    protected void configure(HttpSecurity http) throws Exception {        http.headers().frameOptions().disable();        http.csrf().disable();        http.authorizeRequests().anyRequest().authenticated();        http.formLogin()                .defaultSuccessUrl("/platform/index", true)                .loginPage("/login")                .permitAll()                .and()                .logout();        http.addFilterBefore(ssoFilterSecurityInterceptor(), FilterSecurityInterceptor.class);    }

 

取消安全报文头,如下:

@Configuration      @EnableWebSecurity      public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .headers().disable()                  ...;          }      }

使用部分安全报文头

触发headers()方法的返回结果,例如,只使用HeaderConfigurer的cacheControll()方法和HeadersConfigurer的frameOptions()方法.

@Configuration      @EnableWebSecurity      public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .headers()                      .cacheControl()                      .frameOptions()                      .and()                  ...;          }      }

配置PortMapper

允许配置一个从HttpSecurity的getSharedObject(Class)方法中获取的PortMapper。当http请求跳转到https或者https请求跳转到http请求时(例如我们和requiresChanenl一起使用时),别的提供的SecurityConfigurer对象使用P诶账户的PortMapper作为默认的PortMapper。默认情况下,spring security使用PortMapperImpl来映射http端口8080到https端口8443,并且将http端口的80映射到https的端口443.

配置示例如下,下面的配置将确保在spring security中的http请求端口9090跳转到https端口9443 并且将http端口80跳转到https443端口。

@Configuration      @EnableWebSecurity      public class PortMapperSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/").hasRole("USER")                      .and()                  .formLogin()                      .permitAll()                      .and()                      // Example portMapper() configuration                      .portMapper()                          .http(9090).mapsTo(9443)                          .http(80).mapsTo(443);          }               @Override          protected void configure(AuthenticationManagerBuilder auth) throws Exception {              auth                  .inMemoryAuthentication()                      .withUser("user")                          .password("password")                          .roles("USER");          }      }

配置基于容器的预认证

在这个场景中,servlet容器管理认证。

配置示例:

下面的配置使用HttpServletRequest中的principal,若用户的角色是“ROLE_USER”或者"ROLE_ADMIN",将会返回Authentication结果。

   @Configuration      @EnableWebSecurity      public class JeeSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/").hasRole("USER")                      .and()                  // Example jee() configuration                  .jee()                      .mappableRoles("ROLE_USER", "ROLE_ADMIN");          }      }

开发者希望使用基于容器预认证时,需要在web.xml中配置安全限制。例如:

FORM
/login
/login?error
ROLE_USER
Public
Matches unconstrained pages
/login
/logout
/resources/
Secured Areas
/
ROLE_USER

配置基于X509的预认证

配置示例,下面的配置试图从X509证书中提取用户名,注意,为完成这个工作,客户端请求证书需要配置到servlet容器中。

@Configuration      @EnableWebSecurity      public class X509SecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/").hasRole("USER")                      .and()                  // Example x509() configuration                  .x509();          }      }

配置Remember-me服务

配置示例,下面的配置展示了如何允许基于token的remember-me的认证。若http参数中包含一个名为“remember-me”的参数,不管session是否过期,用户记录将会被记保存下来。

@Configuration      @EnableWebSecurity      public class RememberMeSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                   .inMemoryAuthentication()                        .withUser("user")                             .password("password")                             .roles("USER");          }               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/").hasRole("USER")                      .and()                  .formLogin()                      .permitAll()                      .and()                   // Example Remember Me Configuration                  .rememberMe();          }      }

限制HttpServletRequest的请求访问

配置示例,最基本的示例是配置所有的url访问都需要角色"ROLE_USER".下面的配置要求每一个url的访问都需要认证,并且授权访问权限给用户"admin"和"user".

@Configuration      @EnableWebSecurity      public class AuthorizeUrlsSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/").hasRole("USER")                      .and()                  .formLogin();          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                   .inMemoryAuthentication()                        .withUser("user")                             .password("password")                             .roles("USER")                             .and()                        .withUser("adminr")                             .password("password")                             .roles("ADMIN","USER");          }      }

同样,也可以配置多个url。下面的配置要求以/admin/开始的url访问权限为“admin”用户。

@Configuration      @EnableWebSecurity      public class AuthorizeUrlsSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/admin/**").hasRole("ADMIN")                      .antMatchers("/**").hasRole("USER")                      .and()                  .formLogin();          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                   .inMemoryAuthentication()                        .withUser("user")                             .password("password")                             .roles("USER")                             .and()                        .withUser("adminr")                             .password("password")                             .roles("ADMIN","USER");          }      }

注意:匹配起效是按照顺序来的。因此如果下面的配置是无效的,因为满足第一个规则后将不会检查第二条规则:

http          .authorizeRequests()              .antMatchers("/**").hasRole("USER")              .antMatchers("/admin/**").hasRole("ADMIN")

增加CSRF支持

默认情况下,当使用WebSecurityConfigurerAdapter时的默认构造方法时CSRF是激活的。你可以使用如下方法关闭它:

@Configuration      @EnableWebSecurity      public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .csrf().disable()                  ...;          }      }

增加logout支持

默认支持,当使用WebSecurityConfigurerAdapter时Logout是支持的。当用户发出“/logout”请求时,系统将会销毁session并且清空配置的rememberMe()认证,然后清除SecurityContextHolder,最后跳向logout成功页面或者登陆页面。

@Configuration      @EnableWebSecurity      public class LogoutSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/").hasRole("USER")                      .and()                  .formLogin()                      .and()                  // sample logout customization                  .logout()                         .deleteCookies("remove")                         .invalidateHttpSession(false)                         .logoutUrl("/custom-logout")                         .logoutSuccessUrl("/logout-success");          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                   .inMemoryAuthentication()                        .withUser("user")                             .password("password")                             .roles("USER");          }      }

匿名用户控制

使用WebSecurityConfigurerAdapter时自动绑定。默认情况下,匿名用户有一个AnonymousAuthenticationToken标示,包含角色"ROLE_ANONYMOUS"。

下面的配置展示了如何指定匿名用户应该包含"ROLE_ANON".

@Configuration      @EnableWebSecurity      public class AnononymousSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/").hasRole("USER")                      .and()                  .formLogin()                      .and()                  // sample anonymous customization                  .anonymous()                      .authorities("ROLE_ANON");          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                   .inMemoryAuthentication()                        .withUser("user")                             .password("password")                             .roles("USER");          }      }

基于表单的认证

若FormLoginConfigurer的loginpage(String)没有指定,将会产生一个默认的login页面。

示例配置:

@Configuration      @EnableWebSecurity      public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/**").hasRole("USER")                      .and()                  .formLogin();          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                   .inMemoryAuthentication()                        .withUser("user")                             .password("password")                             .roles("USER");          }      }

下面的示例展示了自定义的表单认证:

@Configuration      @EnableWebSecurity      public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/").hasRole("USER")                      .and()                  .formLogin()                         .usernameParameter("j_username") // default is username                         .passwordParameter("j_password") // default is password                         .loginPage("/authentication/login") // default is /login with an HTTP get                         .failureUrl("/authentication/login?failed") // default is /login?error                         .loginProcessingUrl("/authentication/login/process"); // default is /login with an HTTP post          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                   .inMemoryAuthentication()                        .withUser("user")                             .password("password")                             .roles("USER");          }      }

配置安全通道

为使配置生效,需至少配置一个通道的映射。

配置示例:

下面例子展示了如何将每个请求都使用https通道。

@Configuration      @EnableWebSecurity      public class ChannelSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/**").hasRole("USER")                      .and()                  .formLogin()                      .and()                  .channelSecurity()                      .anyRequest().requiresSecure();          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                   .inMemoryAuthentication()                        .withUser("user")                             .password("password")                             .roles("USER");          }      }

配置http 基本认证

配置示例:

@Configuration      @EnableWebSecurity      public class HttpBasicSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .authorizeRequests()                      .antMatchers("/**").hasRole("USER").and()                      .httpBasic();          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                  .inMemoryAuthentication()                      .withUser("user")                          .password("password")                          .roles("USER");          }      }

配置要触发的HttpRequest

重写RequestMatcher方法、antMatcher()z、regexMatcher()等。

配置示例

下面的配置使HttpSecurity接收以"/api/","/oauth/"开头请求。

@Configuration      @EnableWebSecurity      public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .requestMatchers()                      .antMatchers("/api/**","/oauth/**")                      .and()                  .authorizeRequests()                      .antMatchers("/**").hasRole("USER").and()                      .httpBasic();          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                  .inMemoryAuthentication()                      .withUser("user")                          .password("password")                          .roles("USER");          }      }

下面的配置和上面的相同:

@Configuration      @EnableWebSecurity      public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .requestMatchers()                      .antMatchers("/api/**")                      .antMatchers("/oauth/**")                      .and()                  .authorizeRequests()                      .antMatchers("/**").hasRole("USER").and()                      .httpBasic();          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                  .inMemoryAuthentication()                      .withUser("user")                          .password("password")                          .roles("USER");          }      }

同样也可以这样使用:

@Configuration      @EnableWebSecurity      public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {               @Override          protected void configure(HttpSecurity http) throws Exception {              http                  .requestMatchers()                      .antMatchers("/api/**")                      .and()                  .requestMatchers()                      .antMatchers("/oauth/**")                      .and()                  .authorizeRequests()                      .antMatchers("/**").hasRole("USER").and()                      .httpBasic();          }               @Override          protected void configure(AuthenticationManagerBuilder auth)                  throws Exception {              auth                  .inMemoryAuthentication()                      .withUser("user")                          .password("password")                          .roles("USER");          }      }

 

小结:

   本文是从httpSecurity代码中整理得来的,有助于对spring security的全面理解

http://www.cnblogs.com/davidwang456/p/4549344.html?utm_source=tuicool

 

前一节学习了如何,今天在这个基础上再增加一点新功能:Remember Me. 很多网站,比如博客园,在登录页面就有这个选项,勾选“下次自动登录”后,在一定时间段内,只要不清空浏览器Cookie,就可以自动登录。

一、spring-security.xml 最简单的配置

1     
2 ...3
4

即:在<http></http>节点之间,加一行<rember-me/>,然后

1  
2 ...3

 在<authentication-manager>节点增加一个属性erase-credentials="false" ,配置的修改就算完了

二、登录页login.jsp

1 

 加上这个checkbox勾选框即可

原理简析:按上面的步骤修改后,如果在登录时勾选了Remember Me,登录成功后,会在浏览器中生成一个名为SPRING_SECURITY_REMEMBER_ME_COOKIE的Cookie项,默认有效值为2周,其值是一个加密字符串,其值据说与用户名、密码等敏感数据有关!

下次再进入该页面时,Spring Security的springSecurityFilterChain这个Filter会检测有没有这个Cookie,如果有,就自动登录。

三、安全性分析

安全性分析:这样虽然很方便,但是大家都知道Cookie毕竟是保存在客户端的,很容易盗取,而且cookie的值还与用户名、密码这些敏感数据相关,虽然加密了,但是将敏感信息存在客户端,毕竟不太安全。

建议:对于一些重要操作,比如:电子商务中的在线支付、修改用户密码等需要本人亲自操作的业务环节,还是要将用户引导至登录页,重新登录,以保证安全。为了达到这个目的,代码就必须在jsp前端以java后端,有办法检测出当前登录的用户,是否通过“Remember Me Cookie”自动登录,还是通过“输入用户名、密码”安全登录。

在jsp前端检查是否Remember Me自动登录很简单,直接使用security提供的tag标签即可,类似下面这样:

1 <%@taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>2 ...3 
4 ... 5

在java 服务端的Controller中,可这样检测:

1     /** 2      * 判断用户是否从Remember Me Cookie自动登录 3      * @return 4      */ 5     private boolean isRememberMeAuthenticated() { 6  7         Authentication authentication = SecurityContextHolder.getContext() 8                 .getAuthentication(); 9         if (authentication == null) {10             return false;11         }12 13         return RememberMeAuthenticationToken.class14                 .isAssignableFrom(authentication.getClass());15     }

此外,spring security还提供了remember me的另一种相对更安全的实现机制 :在客户端的cookie中,仅保存一个无意义的加密串(与用户名、密码等敏感数据无关),然后在db中保存该加密串-用户信息的对应关系,自动登录时,用cookie中的加密串,到db中验证,如果通过,自动登录才算通过。

先在db中创建一张表:

1 --Remember Me持久化保存记录 2 create table PERSISTENT_LOGINS 3 ( 4   username  VARCHAR2(64) not null, 5   series   VARCHAR2(64) not null, 6   token     VARCHAR2(64) not null, 7   last_used DATE not null 8 ); 9 10 alter table PERSISTENT_LOGINS11   add constraint PK_PERSISTENT_LOGIN primary key (series);

然后将spring-security.xml中<remember-me/> 改为:

1 

data-source-ref指定数据源,token-validity-seconds表示cookie的有效期(秒为单位),remember-me-parameter对应登录页上checkbox的名字。

这样处理后,勾选Remember me登录会在PERSISTENT_LOGINS表中,生成一条记录:

logout时,该记录以及客户端的cookie都会同时清空。

最后,如果不想用默认的表名persistent_logins,可研究下:

org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl

org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices

这二个类的源码 以及 相关文章:

http://forum.spring.io/forum/spring-projects/security/126343-spring-3-1-persistenttokenbasedremembermeservices-and-usernamepasswordauthentication

http://www.fengfly.com/document/springsecurity3/remember-me.html

http://docs.huihoo.com/spring/spring-security/3.0.x/remember-me.html#remember-me-persistent-token

示例源码:

参考文章:

http://www.cnblogs.com/yjmyzz/p/remember-me-sample-in-spring-security3.html

 

今天在的基础之上,再增加一点新内容,默认情况下Spring Security不会对登录错误的尝试次数做限制,也就是说允许暴力尝试,这显然不够安全,下面的内容将带着大家一起学习如何限制登录尝试次数。

首先对之前创建的数据库表做点小调整

一、表结构调整

T_USERS增加了如下3个字段:

D_ACCOUNTNONEXPIRED,NUMBER(1) -- 表示帐号是否未过期

D_ACCOUNTNONLOCKED,NUMBER(1), -- 表示帐号是否未锁定
D_CREDENTIALSNONEXPIRED,NUMBER(1) --表示登录凭据是否未过期

要实现登录次数的限制,其实起作用的字段是D_ACCOUNTNONLOCKED,值为1时,表示正常,为0时表示被锁定,另外二个字段的作用以后的学习内容会详细解释。

新增一张表T_USER_ATTEMPTS,用来辅助记录每个用户登录错误时的尝试次数

D_ID 是流水号

D_USERNAME 用户名,外建引用T_USERS中的D_USERNAME

D_ATTEMPTS 登录次数

D_LASTMODIFIED 最后登录错误的日期

 

二、创建Model/DAO/DAOImpl

要对新加的T_USER_ATTEMPTS读写数据,得有一些操作DB的类,这里我们采用Spring的JDBCTemplate来处理,包结构参考下图:

T_USER_ATTEMPTS表对应的Model如下

1 package com.cnblogs.yjmyzz.model; 2  3 import java.util.Date; 4  5 public class UserAttempts { 6  7     private int id; 8  9     private String username;10     private int attempts;11     private Date lastModified;12 13     public int getId() {14         return id;15     }16 17     public void setId(int id) {18         this.id = id;19     }20 21     public String getUsername() {22         return username;23     }24 25     public void setUsername(String username) {26         this.username = username;27     }28 29     public int getAttempts() {30         return attempts;31     }32 33     public void setAttempts(int attempts) {34         this.attempts = attempts;35     }36 37     public Date getLastModified() {38         return lastModified;39     }40 41     public void setLastModified(Date lastModified) {42         this.lastModified = lastModified;43     }44 45 }

对应的DAO接口

 
UserDetailsDao

以及DAO接口的实现

 
UserDetailsDaoImpl

观察代码可以发现,对登录尝试次数的限制处理主要就在上面这个类中,登录尝试次数达到阈值3时,通过抛出异常LockedException来通知上层代码。

 

三、创建CustomUserDetailsService、LimitLoginAuthenticationProvider

 
CustomUserDetailsService

为什么需要这个类?因为下面这个类需要它:

 
LimitLoginAuthenticationProvider

这个类继承自org.springframework.security.authentication.dao.DaoAuthenticationProvider,而DaoAuthenticationProvider里需要一个UserDetailsService的实例,即我们刚才创建的CustomUserDetailService

LimitLoginAuthenticationProvider这个类如何使用呢?该配置文件出场了

 

四、spring-security.xml

1 
7 8
9
10
11
12
15
16
17
18 19
21
22
23 24
26
28
30
31
32 33
35
36
37
38
39 40
42
43
44 45 46
47
48
49 50

跟之前的变化有点大,47行是核心,为了实现47行的注入,需要33-38行,而为了完成authenticationProvider所需的一些property的注入,又需要其它bean的注入,所以看上去增加的内容就有点多了,但并不难理解。

 

五、运行效果

连续3次输错密码后,将看到下面的提示

这时如果查下数据库,会看到

错误尝试次数,在db中已经达到阀值3

而且该用户的“是否未锁定”字段值为0,如果要手动解锁,把该值恢复为1,并将T_USER_ATTEMPTS中的尝试次数,改到3以下即可。

 

源代码下载:

参考文章: 

http://www.cnblogs.com/yjmyzz/p/limit-login-attempts-in-spring-security3.html

在前一节里,我们学习了如何把“登录帐号、密码”存储在db中,但是密码都是明文存储的,显然不太讲究。这一节将学习如何使用spring security3新加入的bcrypt算法,将登录加密存储到db中,并正常通过验证。

一、Bcrypt算法

1 int t = 0; 2 String password = "123456"; 3 System.out.println(password + " -> "); 4 for (t = 1; t <= 10; t++) { 5     BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); 6     String hashedPassword = passwordEncoder.encode(password); 7     System.out.println(hashedPassword); 8 } 9 10 password = "MIKE123";11 System.out.println(password + " -> ");12 for (t = 1; t <= 10; t++) {13     BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();14     String hashedPassword = passwordEncoder.encode(password);15     System.out.println(hashedPassword);16 }

输出如下:

 123456 -> 

 2a2a10$.Cjkvbgr2JzGkag9IdbT.Oc/sbY7wVqLgAHws7HCxqcI7eczKtCLq
 2a2a10$OCOuRV0Wy7ncCND4LcKfMunVEWOzMOyyU95u5TkTRmJqYbsJNecEK
 2a2a10$TXttsDZUaeEb2zX6wiwN0eqREKFoCDyh81Kfa6BgAcZ2hyqPNC0Ra
 2a2a10$FfLx/gxq.FyeOBb0nbaVeusLhQjASSdY7w45i1ACl/rcYQMmhaXV2
 2a2a10$JdPXAxmuz.WTP5gxYiYseeKRSM/HTFzJJdACcDQ4MdhaaLmC0SjI.
 2a2a10$yVEWf2MrwjCyi51rUKqQle/MZb7vwcOf6Gwp.hDT2ZUchlyAtJ4pO
 2a2a10$FfJg2ATit7btKfJovL6zmug//8rzToQn7FO.fxOzo1KtNNfhWKuca
 2a2a10$pOLMkd13n7i3DtVijLEqze1zeURpjtVz5rAx1qOAPqCQvjGG/d6D.
 2a2a10$fQ32i8JsjjmqVRpiEsgT3ekTKtrfXn.JNl69beWEx0.YgdX.SEx5e
 2a2a10$78brJFSdftip0XXYx4rS6ewdu4SiSsMIBY9oNcLhAZwg3GysRGk2m
 MIKE123 -> 
 2a2a10$U6KVh1NGxAIGYiM4YVgn6OAQt6ayAoLkh2lODv16rSpkS1iqfbR2C
 2a2a10$t0FlEOBLEB8VwWJVoZRrweIRV0XyoBgm29c0SMqfqRK3ZBuvhgYbS
 2a2a10$QpW6nHnWNhbTTjLq/NbzBu2Unp8ijwyPeUx2N2eMFWReFezosZ5fi
 2a2a10$LtPzoQU0IluAgvP3/WhWquUv2AcDRh2ENhAeWDquiN/spitZYe/7q
 2a2a10$Qcx7vUudzF7qzTjz.QpLKOby0tXQ4j.uqkInS1n4/6oD2r2eL0rZW
 2a2a10$yZw7cdq1y9sjX8nZhYynseWjQ4jeVv76fPmBl.sg2xPvb8cyXD8Sq
 2a2a10$kTmT6BQQE5LyRZ00Qas77.F5kxK0GxsW402ExosQswxmG.eBdgIZW
 2a2a10$SRfHDNM.m3qX5y1O7V/cp.hQqgaXnKzfxBGRhLkAF39bufejuOieu
 2a2a10$Sw5w2kTImJ5Y8UNlE/5/9OLaUgYxhCXU3P3gFBdEbs9PL8pCl60Q2
 2a2a10$0mN8kNAl9GNr0c4K1Nr0b.MIcBW0QcPHB/f20hgeBuRfwvgZXT6hG
从以上输出结果发现bcrypt算法与md5/sha算法有一个很大的区别,每次生成的hash值都是不同的,这样暴力猜解起来或许要更困难一些。同时大家可能也发现了,加密后的字符长度比较长,有60位,所以用户表中密码字段的长度,如果打算采用bcrypt加密存储,字段长度不得低于60.

 

转载于:https://www.cnblogs.com/softidea/p/6243200.html

你可能感兴趣的文章
VMware Data Recovery备份恢复vmware虚拟机
查看>>
solr多core的处理
查看>>
解决DeferredResult 使用 @ResponseBody 注解返回中文乱码
查看>>
C# WinForm开发系列 - TextBox
查看>>
28岁少帅统领旷视南京研究院,LAMDA魏秀参专访
查看>>
java文件传输
查看>>
Xen虚拟机迁移技术
查看>>
SQL Server配置delegation实现double-hop
查看>>
iOS开发之检查更新
查看>>
安装Sql Server 2005出现“性能监视器计数器要求”错误解决方法。
查看>>
[.NET领域驱动设计实战系列]专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现...
查看>>
Icomparer和Icomparable集合排序
查看>>
【poi xlsx报错】使用POI创建xlsx无法打开
查看>>
UNIX环境高级编程笔记之文件I/O
查看>>
DIV+CSS规范命名
查看>>
4G U盘版64位bitcoin专用挖矿操作系统
查看>>
我的2013 Q.E.D
查看>>
2017 Multi-University Training Contest - Team 9 1002&&HDU 6162 Ch’s gift【树链部分+线段树】...
查看>>
4.5. Rspamd
查看>>
超级简单:在你的ASP.NET页面自定义列表和分页
查看>>