Spring Security表单登录认证

Spring Security是一个强有力并且高度定制化的认证和访问控制框架,致力于为Java应用程序提供认证和授权。


特性:
1、为认证和授权提供综合性和扩展性支持。
2、免受session定位、点击劫持、跨站点请求伪装等攻击。
3、Servelt API集成。
4、与Spring MVC集成

一、Spring Security架构


1、认证


AuthenticationManager是主要的认证策略接口,该接口只包含一个方法。
public interface AuthenticationManager {

  Authentication authenticate(Authentication authentication)
    throws AuthenticationException;

}

认证管理器通过authenticate方法可做下面3处理: 
(1)如果能证明输入是一个合理的用户标识,则返回Authentication对象。
(2)如果输入不是一个合理的用户标识,抛出AuthenticationExcepton。
(3)如果无法决策则返回null。
备注:AuthenticationException为运行时异常,根据需要进行处理。

AuthenticationManager常用的实现为ProviderManager,ProviderManager会委托给AuthenticationProvider实例链。
AuthenticationProvider和AuthenticataionManager相似,但提供了额外的方法允许调用者查询是否支持给定的Authentication类型。

public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;

    boolean supports(Class<?> authentication);
}

2、授权(访问控制)


认证成功后就是授权,核心策略是AccessDecisionManager,框架提供了访问决策管理器3种实现,它们都会委托给AccessDecisionVoter,就像
ProviderManager委托给AuthenticationProvider。

public interface AccessDecisionVoter {

    boolean supports(ConfigAttribute attribute);

扫描二维码关注公众号,回复: 4272376 查看本文章

    boolean supports(Class<?> clazz);

    int vote(Authentication authentication, S object,
            Collection<ConfigAttribute> attributes);
}

object参数代表用户想要访问的任何事物(如:web或者java类中的方法),ConfigAttributes代表访问资源所需的许可证等级,
ConfigAttribute是一个接口,只有一个getAttribute()方法,返回值为谁允许访问资源的表达式规则,例如用户角色的名字:ROLE_ADMIN或者ROLE_AUDIT,一般以ROLE_前缀开头。

二、Web Security


1、Spring security在web层是基于Filters的,下面是单个http请求的处理层级:
Client <-> Filter <-> Filter <-> Filter <-> Servlet
客户单发送请求到app,由容器来决定使用哪些filters和servelt,一个请求最多一个servlet来处理,但能有多个filters,多个filters有排序,一个filter能否决其它剩余filter,并且能修改下游filter
和servlet的请求与响应。

Spring security在过滤器链中注册为单个过滤器,具体类型为FilterChainProxy,默认被注册为@Bean来处理所有请求。下面是有过滤器链代理后单个http请求的处理层级:
Client <-> Filter <-> FilterChainProxy(到多个filters) <-> Filter <-> Servlet
备注:事实上在安全过滤器中还有隐含的一层,DelegatingFilterProxy,该代理会委托给FilterChainProxy(在容器中为一个@Bean,并且bean的名字固定为springSecurityFilterChain,该bean包含了所有
安全相关的逻辑)

2、FilterChainProxy下面可以有多个由spring security管理的过滤器链,并且会把请求分发到第一个匹配到的过滤器链,有且只有一个过滤器链来处理。
            FilterChainProxy
/foo/**            /bar/**            /**
Filter            Filter            Filter
Filter            Filter            Filter
Filter            Filter            Filter
注意:/foo/**会比/**先匹配到,匹配规则:精确匹配 > 路径匹配 > 通配符匹配 > 缺省匹配

3、一般没有其它自定义的FilterChainProxy有5个或过滤器链,第一个用来忽略静态资源,像/css/**,/images/**以及错误视图/error。最后一个过滤器链匹配/**,
包含认证,授权,异常处理,会话处理,头部写入等等。

三、Java配置


1、表单登录认证

(1)配置类继承自WebSecurityConfigurerAdapter,并带上@EnableWebSecurity注解。

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()// 限制基于HttpServletReqeuest的请求访问
          .antMatchers("/", "/home").permitAll()// /和/home路径能被任何人访问
          .anyRequest().authenticated()// 其它请求需要身份认证
          .anyRequest().hasRole("USER")// 其它请求必须是USER角色,该方法默认会加上ROLE_前缀
        .and()
          .formLogin()// 支持基于表单的身份认证
          .loginPage("/login")// 指定跳转到登录页的url,若不指定则会生成默认登录页面,默认为/login
          .loginProcessingUrl("/loginProcess")// 指定认证处理的url,表单action指定地址必须为该地址,默认为/login
          .defaultSuccessUrl("/success")// 认证成功后默认跳转的地址,默认为/home
          .failureUrl("/loginProcess?error")// 认证失败后跳转的地址,默认为/login?error
          .permitAll()// 给用户所有与表单登录相关的url访问授权
        .and()
          .rememberMe()// 开启记住我的功能
          .rememberMeCookieName("remember-me")// 传给浏览器的cookie名,默认为remember-me
          .rememberMeParameter("remember-me")// 前端复选框传入的字段名,默认为remember-me
          .tokenValiditySeconds(30 * 1000)// cookie有效时间
          .key(UUID.randomUUID().toString())// 防止名为remember-me的token被修改的key,默认为随机数,生成随机数需要时间,最好指定固定值
        .and()
          .logout()// 开启退出登录支持
          .logoutUrl("/logout")// 触发退出登录的url,前端页面地址必须为改地址,默认为/logout
          .logoutSuccessUrl("/loginProcess?logout")// 退出登录跳转的地址
          .deleteCookies("remember-me")// 退出登录后删除名为remember-me的cookie,默认会删除remember-me功能对应的cookie
          .permitAll()// 授权所有与退出登录相关的url
        .and()
          .csrf().disable();// 禁用csrf,不禁用则要在表单里加上隐藏域或csrf标签
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 创建DelegatingPasswordEncoder,该PasswordEncoder会使用BCryptPasswordEncoder对密码进行编码
    PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String encodedPwd = encoder.encode("123");
    // 传入的密码不能为原密码,必须经过编码,编码后的格式为{编码器Id}+编码后的密码
    auth.inMemoryAuthentication().withUser("lyl").password(encodedPwd).roles("USER");
  }

  /**
   * 不重写configure(AuthenticationManagerBuilder auth)方法可以在容器中注册UserDetailService实例
   */
  // @Bean
  // public UserDetailsService userDetailsService() {
  // String encodedPwd = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123");
  // UserDetails userDetails = User.withUsername("lyl").password(encodedPwd).roles("USER").build();
  // return new InMemoryUserDetailsManager(userDetails);
  // }
  
}

(2)Web MVC相关配置

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/success").setViewName("success");
    registry.addViewController("login").setViewName("login");
  }

  @Override
  public void configureViewResolvers(ViewResolverRegistry registry) {
    registry.jsp("/WEB-INF/jsp/", ".jsp");
  }

}

@Controller
public class AuthController {

  @RequestMapping("/loginProcess")
  public String handelRequest(String error, String logout) {
    // 认证失败重新跳转到登录页面
    if (error != null) {
      return "login";
    }

    // 如果是退出登录,则跳转到退出登录后的页面
    if (logout != null) {
      return "logout";
    }

    return "success";
  }
}

(3)前端jsp表单

<!-- 注意:请求方式必须为post,且action地址必须为loginProcessingUrl配置的地址 -->
<form action="loginProcess" method="post">
	<!-- 如果没禁用csrf则要加上隐藏域,用于传csrf token给后台-->
	<!-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> -->
	<table>
		<tr>
			<td>用户名:</td>
			<td><input name="username" type="text" /></td>
		</tr>
		<tr>
			<td>密码:</td>
			<td><input name="password" type="password" /></td>
		</tr>
		<tr>
			<!-- 上传字段名必须和后端Java配置一致,默认为remember-me,用户名和密码也必须与后端配置保持一致,默认为username和password-->
			<td>记住我<input name="remember-me" type="checkbox" checked="checked" /></td>
		</tr>
		<tr>
			<td><input type="submit" value="登录"></td>
		</tr>
	</table>
</form>

猜你喜欢

转载自blog.csdn.net/lingbomanbu_lyl/article/details/84560399