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);
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>