1、简介
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。
2、与Shiro区别:
Shiro是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加
密。如下是它所具有的特点:
①易于理解的 Java Security API;
②简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory
等);
③对角色的简单的鉴权(访问控制),支持细粒度的鉴权;
④支持一级缓存,以提升应用程序的性能;
⑤内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
⑥异构客户端会话访问;
⑦非常简单的加密 API;
⑧不跟任何的框架或者容器捆绑,可以独立运行。
Spring Security:
除了不能脱离Spring,shiro功能Security都具备;并且Spring Security对Oauth openid也有支持,shiro需要手动实现,Spring Security权限颗粒度也更高
3、应用场景
①用户登录,基于web开发的项目登录功能
②用户授权
③单一登录:一个账号同一时间只能在一个地方进行登录,如果在其他登录二次登录,则剔除前面的登录操作
④集成CAS,做单点登录
⑤集成Oauth2,做授权登录(第三方登录等),也可以实现cas
4.Spring Security认证基本原理
①使用方式:引入依赖
org.springframework.boot
spring-boot-starter-security
②原理:
Spring Security功能的实现主要是由一系列过滤器相互配合完
成。也称之为过滤器链
https://www.processon.com/diagraming/60889b756376896ee106ae10
③认证方式:
⑴HttpBasic认证:Spring Security实现登录验证最简单的一种方式,Security 4.X版本,无需任何配置,启动项目访问则会弹出默认的httpbasic认证,在spring security 5.x默认的验证模式已经是表单模式。HttpBasic模式要求传输的用户名密码使用Base64模式进行加密。
⑵formLogin登录认证模式:spring boot2.0以上版本(依赖Security 5.X版本)默认会生成一个登录页面.同样,我们也可以选择自定义登录页面
④安全构建器 HttpSecurity 和 WebSecurity 的区别:
⑴、WebSecurity 不仅通过 HttpSecurity 定义某些请求的安全控制,也通过其他方式定义其他某些请求可以忽略安全控制;
⑵、HttpSecurity 仅用于定义需要安全控制的请求(当然 HttpSecurity 也可以指定某些请求不需要安全控制);
⑶、可以认为 HttpSecurity 是 WebSecurity 的一部分, WebSecurity 是包含 HttpSecurity 的更大的一个概念;
⑷、构建目标不同:WebSecurity 构建目标是整个 Spring Security 安全过滤器 FilterChainProxy`;HttpSecurity 的构建目标仅仅是 FilterChainProxy 中的一个 SecurityFilterChain 。
⑤表单登录:
⑴UsernamePasswordAuthenticationFilter过滤器源码分析
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");
表单中的input的name值是username和password, 并且表单提交的路径为 /login , 表单提交方式method为 post , 这些可以修改为自定义的值.即在自定义SecurityConfig配置类中:
http.formLogin()//开启表单认证
.loginPage("/toLoginPage")//自定义登录页面
.loginProcessingUrl("/login")//自定义表单提交路径
.usernameParameter("username")
.passwordParameter("password")//自定义input值
.successForwardUrl("/")//指定登录成功后跳转的路径
.and().authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登陆页面
.anyRequest().authenticated();
/**
* 关闭csrf防护
*/
http.csrf().disable();
/**
* 加载同源域名下iframe页面
*/
http.headers().frameOptions().sameOrigin();
⑵基于数据库实现认证功能:
1、实现security的一个UserDetailsService接口, 重写这个接口里面
loadUserByUsername方法:
/**
* 根据username查询用户实体
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if(user==null){
throw new UsernameNotFoundException(username+"用户没有找到");
}
//声明权限集合,因为构造方法中不能传null值
Collection extends GrantedAuthority> authorities = new ArrayList<>();
UserDetails userDetails=new org.springframework.security.core.userdetails.User(user.getUsername()
,"{bcrypt}"+user.getPassword() //{noop}表示不加密 bcrypt表示bcrypt算法加密
,true //用户是否启用 true 代表启用
,true // 用户是否过期 true 代表未过期
,true // 用户凭据是否过期 true 代表未过期
,true // 用户是否锁定 true 代表未锁定
,authorities);
return userDetails;
}
2、在SecurityConfig配置类中指定自定义用户认证
/**
* 身份验证管理器
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//使用自定义用户认证
auth.userDetailsService(myUserDetailsService);
}
⑶密码加密认证:
在基于数据库完成用户登录的过程中,我们所是使用的密码是明文的,规则是通过对密码明文添加{noop} 前缀。
Spring Security 中 PasswordEncoder 就是我们对密码进行编码的工具接口。该接口只有两个功能:一个是匹配验证。另一个是密码编码
public interface PasswordEncoder {
String encode(CharSequence var1);
boolean matches(CharSequence var1, String var2);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
密码工厂PasswordEncoderFactories:
public class PasswordEncoderFactories {
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map encoders = new HashMap();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new LdapShaPasswordEncoder());
encoders.put("MD4", new Md4PasswordEncoder());
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new StandardPasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
private PasswordEncoderFactories() {
}
}
⑦退出登录:LogoutSuccessHandler处理器
//设置退出url
//自定义退出处理
and().logout().logoutUrl("/logout").logoutSuccessHandler(myAuthenticationService)
⑧图形码验证:
spring security添加验证码大致可以分为三个步骤:
⑴、根据随机数生成验证码图片;
⑵、将验证码图片显示到登录页面;
⑶、认证流程中加入验证码校验。
Spring Security的认证校验是由UsernamePasswordAuthenticationFilter过滤器完成的,所以验证码校验逻辑应该在这个过滤器之前:
自定义验证码过滤器ValidateCodeFilter,需要继承OncePerRequestFilter确保在一次请求只通过一次filter,而不 需要重复执行
⑨session管理:
⑴、会话超时:
设置session管理和失效后跳转地址
http.sessionManagement() //设置session管理
.invalidSessionUrl("/toLoginPage")// session无效后跳转的路径, 默认是登录页面
⑵、并发控制:SessionManagementFilter
修改超时时间:
#session设置 #配置session超时时间
server.servlet.session.timeout=600
设置最大会话数量:
http.sessionManagement() //设置session管理
.invalidSessionUrl("/toLoginPage") // session无效后跳转的路径, 默 认是登录页面
.maximumSessions(1)//设置session最大会话数量 ,1同一时间只能有一个 用户登录
.expiredUrl("/toLoginPage");//设置session过期后跳转路径
阻止用户第二次登录:sessionManagement也可以配置maxSessionsPreventsLogin:boolean值,当达到maximumSessions设置的最大会话个数时阻止登录。
http.sessionManagement()//设置session管理
.invalidSessionUrl("/toLoginPage")// session无效后跳转的路径, 默 认是登录页面
.maximumSessions(1)//设置session最大会话数量 ,1同一时间只能有一个 用户登录
.maxSessionsPreventsLogin(true)//当达到最大会话个数时阻止登录。
.expiredUrl("/toLoginPage");//设置session过期后跳转路径
5、SpringSecurity授权
①内置表达式:
Spring Security 使用Spring EL来支持,主要用于Web访问和方法安全上, 可以通过表达式来判断是否具有访问权限. 下面是Spring Security常用的内置表达式.ExpressionUrlAuthorizationConfigurer定义了所有的表达式
表达式 说明
permitAll 指定任何人都允许访问。
denyAll 指定任何人都不允许访问
anonymous 指定匿名用户允许访问。
rememberMe 指定已记住的用户允许访问。
authenticated 指定任何经过身份验证的用户都允许访问,不包含anonymous
fullyAuthenticated 指定由经过身份验证的用户允许访问,不包含anonymous和rememberMe
hasRole(role) 指定需要特定的角色的用户允许访问, 会自动在角色前面插入'ROLE_'
hasAnyRole([role1,role2]) 指定需要任意一个角色的用户允许访问, 会自动在角色前面插入'ROLE_'
hasAuthority(authority) 指定需要特定的权限的用户允许访问
hasAnyAuthority([authority,authority]) 指定需要任意一个权限的用户允许访问
hasIpAddress(ip) 指定需要特定的IP地址可以访问
②url安全表达式:
⑴、设置url访问权限:
// 设置/user/** 访问需要ADMIN角色
http.authorizeRequests().antMatchers("/user/**").hasRole("ADMIN");
// 设置/user/** 访问需要PRODUCT角色和IP地址为127.0.0.1
.hasAnyRole("PRODUCT,ADMIN")
.http.authorizeRequests().antMatchers("/product/**") .access("hasAnyRole('ADMIN,PRODUCT') and hasIpAddress('127.0.0.1')");
// 设置自定义权限不足信息
.http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
⑵、 MyAccessDeniedHandler自定义权限不足类,实现AccessDeniedHandler处理器
⑶、设置用户对应的角色权限
③在Web 安全表达式中引用自定义Bean授权:
⑴、定义自定义授权类,类似:
/*** 自定义授权类 */
@Component public class MyAuthorizationService {
/*** 检查用户是否有对应的访问权限
** @param authentication 登录用户
* @param request 请求对象
* @return */
public boolean check(Authentication authentication, HttpServletRequest request) {
User user = (User) authentication.getPrincipal();
// 获取用户所有权限
Collection authorities = user.getAuthorities();
// 获取用户名
String username = user.getUsername();
// 如果用户名为admin,则不需要认证
if (username.equalsIgnoreCase("admin")) {
return true;
} else {
// 循环用户的权限, 判断是否有ROLE_ADMIN权限, 有返回true
for (GrantedAuthority authority : authorities) {
String role = authority.getAuthority();
if ("ROLE_ADMIN".equals(role)) {
return true;
}
}
}
return false;
}
}
⑵、配置类
//使用自定义Bean授权
http.authorizeRequests().antMatchers("/user/**")
.access("@myAuthorizationService.check(authentication,request)");
⑶、携带路径变量
//使用自定义Bean授权,并携带路径参数
http.authorizeRequests().antMatchers("/user/delete/{id}").
access("@myAuthorizationService.check(authentication,request,#id)");
④Method安全表达式:
针对方法级别的访问控制比较复杂, spring security 提供了4种注解分别是:
@PreAuthorize :注解适合进入方法前的权限验证
@PostAuthorize :在方法执行后再进行权限验证,适合验证带有返回值的权
限, Spring EL 提供返回对象能够在表达式语言中获取到返回对象的 returnObject
@PreFilter : 可以用来对集合类型的参数进行过滤, 将不符合条件的元素剔除集合
@PostFilter : 可以用来对集合类型的返回值进行过滤, 将不符合条件的元素剔除集合