SpringSecurity
一、SpringSecurity介绍
1、介绍
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的事实上的标准。
Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。像所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足定制需求的能力。
主要功能:
1、认证 (你是谁)
2、授权 (你能干什么)
3、攻击防护 (防止伪造身份)
2、特征
对身份验证和授权的全面且可扩展的支持
保护免受会话固定,点击劫持,跨站点请求伪造等攻击
Servlet API集成
与Spring Web MVC的可选集成
多得多…
3、使用条件
j8以上
二、SpringSecurity使用
1、导包
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
gradle
dependencies {
compile "org.springframework.boot:spring-boot-starter-security"
}
2、继承WebSecurityConfigurerAdapter开始操作
(1)、重写configure方法
简单整理下为什么开启跨域
如果没有同源策略,不同源的数据和资源(如HTTP头、Cookie、DOM、localStorage等)就能相互随意访问,根本没有隐私和安全可言。为了安全起见和资源的有效管理,浏览器当然要采用这种策略。
@Component
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//开启跨域
http.formLogin().and().cors().and().
//后面都是授权配置
authorizeRequests()
//匹配 许可证 (该请求放行)
.antMatchers("/demoController").permitAll()
//或者除了放行的url拦截所有请求
.anyRequest().authenticated();
}
}
如果说没有请求放行则会返回如下
(2)、内置控制访问方法
瞅一瞅源码可得知
static final String permitAll = "permitAll";...............................................permitAll是允许所有
private static final String denyAll = "denyAll";...........................................denyAll是拒绝所有
private static final String anonymous = "anonymous";.......................................anonymous是允许匿名的
private static final String authenticated = "authenticated";...............................authenticated是需要认证
private static final String fullyAuthenticated = "fullyAuthenticated";.....................fullyAuthenticated是需要完整的认证
private static final String rememberMe = "rememberMe";.....................................rememberMe是记住,比如7天免登录这种
(3)、登录页
SpringBoot自带登录页开启 formLogin 后地址加端口即可访问,账号随意密码在项目启动时会在控制台打印
//使用Springboot自带的登录页
http.formLogin().and()
//后面都是授权配置
authorizeRequests()
//匹配 证明 (拦截全部请求)
.antMatchers("/**").authenticated();
(4)、使用自定义登录页
http.formLogin()
//当发现login时认为是登录需要执行我们自定义的登录逻辑 >里面的url是登录页面表单的提交地址
.loginProcessingUrl("/login")
//登录成功后请求地址 请求方法必须是post的
.successForwardUrl("/toMain")
//设置登录页面
.loginPage("/login.html")
//放行登录页面
.and
.authorizeRequests()
.antMatchers("/login").permitAll()
//关闭csrf防护 >只有关闭了,才能接受来自表单的请求
http.csrf().disable();
扯一嘴csrf攻击就是利用浏览器返回的Cookies和session
(5)、登出页
看源码注释会发现登出可以做很多操作,例如删除cookie
//登出后跳转页面
http.logout().logoutSuccessUrl("/");
附上api
表达式 | 说明 |
---|---|
* hasRole([role]) | 当前账户有指定角色时返回true , 默认情况下,角色都是以ROLE_ 开头,当然也可以在修改DefaultWebSecurityExpressionHandler 中修改defaultRolePrefix 自定义角色前缀 |
* hasAnyRole([role1,role2]) | 当前账户有指定角色中的任意一个时返回true , 默认情况下,角色都是以ROLE_ 开头,当然也可以在修改DefaultWebSecurityExpressionHandler 中修改defaultRolePrefix 自定义角色前缀 |
hasAuthority([authority]) | 当前账户有指定权限时返回true |
hasAnyAuthority([authority1,authority2]) | 当前账户有指定权限中任何一个时返回true |
principal | 允许当前用户直接访问的对象主体 |
authentication | 允许直接访问从SecurityContext获得的当前身份验证对象 |
permitAll | 允许所有 |
denyAll | 拒绝所有 |
isAnonymous() | 是否匿名用户 |
isRememberMe() | 当前是否被记住 |
* isAuthenticated() | 是否已经登录 |
isFullyAuthenticated() | 是否已经登录 或 被记住 |
* hasPermission(Object target, Object permission) | Returns true if the user has access to the provided target for the given permission. For example, hasPermission(domainObject, ‘read’) |
* hasPermission(Object targetId, String targetType, Object permission) | Returns true if the user has access to the provided target for the given permission. For example, hasPermission(1, ‘com.example.domain.Message’, ‘read’) |
hasIpAddress([ip address]) | IP地址是否是??? |
三、角色权限判断
利用@PreAuthorize注解自定义权限校验
开启@EnableGlobalMethodSecurity(prePostEnabled = true)注解, 在继承 WebSecurityConfigurerAdapter 这个类的类上面贴上这个注解.并且prePostEnabled设置为true,@PreAuthorize这个注解才能生效,SpringSecurity默认是关闭注解功能的.
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
(1)、UserDetailsService(基于数据库自定义UserDetailsService实现认证)
UserDetailsService接口用于返回用户相关数据。它有loadUserByUsername()方法,根据username查询用户实体,可以实现该接口覆盖该方法,实现自定义获取用户过程。该接口实现类被DaoAuthenticationProvider 类使用,用于认证过程中载入用户信息。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final String user = "admin";
private final String pas = "$2a$10$kkPpRCSQJii1BdV77TqAbuWtBFAvtUGqFor.AbyGxGT.avFH/buU2";
@Override
//通过username去数据库查询账号密码
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> authorityList = new ArrayList<>();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + "admin");
authorityList.add(authority);
SimpleGrantedAuthority authority1 = new SimpleGrantedAuthority("user:add");
authorityList.add(authority1);
return new User(user,pas,authorityList);
}
}
//当然实际场景中账号密码和权限列表是通过数据库的查询获得的,最终返回一个User对象即可
//Security执行的权限检查不管你角色还是权限,他只比较字符
GrantedAuthority 有三个实现类,通常选用核心包中的SimpleGrantedAuthority(单纯形权限)
(2)、配置Security
需要注意的是 WebSecurityConfigurerAdapter中有重载了三个configure
configure(AuthenticationManagerBuilder)认证管理器配置
用于通过允许轻松添加AuthenticationProviders来建立身份验证机制
configure(HttpSecurity)安全过滤器链配置
允许基于选择匹配在资源级别配置基于Web的安全性
configure(WebSecurity)核心过滤器配置
用于影响全局安全性的配置设置(忽略资源,设置调试模式,通过实现自定义防火墙定义拒绝请求),可设置UserDetails 和加密方式
通常使用过滤器链 configure(HttpSecurity)用来配置 HttpSecurity 。 HttpSecurity 用于构建一个安全过滤器链 SecurityFilterChain 。SecurityFilterChain 最终被注入核心过滤器 。
@Component
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
UserDetailsService userDetailsService;
/**
* 配置密码解析
* @return
*/
@Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin().successForwardUrl("/index")
.and()
.authorizeRequests()
.anyRequest()//所有请求
.authenticated();//都需要身份认证
}
}
(3)、请求鉴权(@PreAuthorize)
@PreAuthorize("hasAuthority('user:add')")
public String create(){
return "添加功能:create";
}
需要给每个请求使用 @PreAuthorize("hasAuthority('user:add')") 标识当然也可以用 @PreAuthorize("hasAnyRole('ROLE_admin')")
注意:一定要以大写ROLE_开头后面随意(指定角色可以访问)
四、SpringSecurity提供支持表达式的注解
(1)、@PreAuthorize
可以用来控制一个方法是否能够被调用。也就是说先判断后执行
(2)、@PostAuthorize
先调用该接口,获得结果后在去判断该用户是否有权限(有意思的是如果接口有返回值用户没有权限最终不去返回结果罢了,但是操作还是做了)
(3)、@PreFilter
对集合类型的参数或返回值进行过滤,Spring Security将移除使对应表达式的结果为false的元素。
(4)、@PostFilter
注意@PostFilter注解只有在控制器方法的return返回值是一个集合的时候才可以使用;@PostFilter注解的作用:如果控制器方法的return返回值是一个集合,此注解可以对return的这个集合进行过滤输出;
(5)、@Secured注解
@Secured注解的作用:在用户向浏览器发送一个请求时会去访问控制器中的方法,然后在访问此控制器中的方法之前会先去UserDetailsService用户细节实现类的实现方法中return的User对象查看是否具有@Secured注解中指定的角色,如果有指定的角色,那么系统允许用户访问此控制器方法,否则,系统不允许访问此控制器方法;注意在使用@Secured设置角色名字的时候,角色名的前面一定要加上ROLE_前缀;
五、OAuth2
1、简介
很多网站、APP 弱化甚至没有搭建自己的账号体系,而是直接使用社会化登录的方式,这样不仅免去了用户注册账号的麻烦、还可以获取用户的好友关系来增强自身的社交功能。
比如我们可以使用微博登录简书,简书会自动将你的微博头像设置为你的简书头像,将你的微博昵称设置为你的简书昵称,甚至还可以获取你微博中的好友列表,提示你哪些朋友已经在使用简书,这是如何做到的呢?
最传统的办法是让用户直接在简书的登录页面输微博的账号和密码,简书通过用户的账号和密码去微博那里获取用户数据,但这样做有很多严重的缺点:
简书需要明文保存用户的微博账号和密码,这样很不安全。
简书拥有了获取用户在微博所有的权限,包括删除好友、给好友发私信、更改密码、注销账号等危险操作。
用户只有修改密码,才能收回赋予简书的权限。但是这样做会使得其他所有获得用户授权的第三方应用程序全部失效。
只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有使用微博登录的网站的数据泄漏。
为了解决以上的问题,OAuth 协议应运而生。
2、原理概要
新浪微博作为服务提供商,拥有用户的头像、昵称、邮箱、好友以及所有的微博内容,简书希望获取用户存储在微博的头像和昵称,假设它们是三个人:
1、简书问新浪微博:我想要获取用户 A 的头像和昵称,请你提供
2、微博说:我需要经过用户A 本人的许可,然后去问用户 A 是否要授权简书访问自己的头像和昵称
3、用户 A 对微博说:我给简书一个临时的钥匙,如果他给你出示了这把钥匙,你就把我的资料给他
4、简书使用户给它的钥匙获取用户头像和昵称信息。
也就是说这玩意就是搞第三方授权的咳咳。。。
3、流程图
4、授权模式
(1)、授权码模式
授权码模式是四种模式中最繁琐也是最安全的一种模式。
client向资源服务器请求资源,被重定向到授权服务器(AuthorizationServer)
浏览器向资源拥有者索要授权,之后将用户授权发送给授权服务器
授权服务器将授权码(AuthorizationCode)转经浏览器发送给client
client拿着授权码向授权服务器索要访问令牌
授权服务器返回Access Token和Refresh Token给cilent
这种模式是四种模式中最安全的一种模式。一般用于client是Web服务器端应用或第三方的原生App调用资源服务的时候。因为在这种模式中AccessToken不会经过浏览器或移动端的App,而是直接从服务端去交换,这样就最大限度的减小了AccessToken泄漏的风险。
(2)、简化模式/隐式授权模式
简化模式相对于授权码模式省略了,提供授权码,然后通过服务端发送授权码换取AccessToken的过程。
client请求资源被浏览器转发至授权服务器
浏览器向资源拥有者索要授权,之后将用户授权发送给授权服务器
授权服务器将AccessToken以Hash的形式存放在重定向uri的fargment中发送给浏览器
浏览器访问重定向URI
资源服务器返回一个脚本,用以解析Hash中的AccessToken
浏览器将Access Token解析出来
将解析出的Access Token发送给client
一般简化模式用于没有服务器端的第三方单页面应用,因为没有服务器端就无法使用授权码模式。
(3)、密码模式
用户将认证密码发送给client
client拿着用户的密码向授权服务器请求Access Token
授权服务器将Access Token和Refresh Token发送给client
这种模式十分简单,但是却意味着直接将用户敏感信息泄漏给了client,因此这就说明这种模式只能用于client是我们自己开发的情况下。因此密码模式一般用于我们自己开发的,第一方原生App或第一方单页面应用。
(4)、客户端模式
这是一种最简单的模式,只要client请求,我们就将AccessToken发送给它。
client向授权服务器发送自己的身份信息,并请求AccessToken
确认client信息无误后,将AccessToken发送给client
这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。因此这种模式一般用来提供给我们完全信任的服务器端服务。在这个过程中不需要用户的参与。
(5)、四种模式的应用场景
1、授权码模式:第三方Web服务器端应用与第三方原生App
2、简化模式:第三方单页面应用
3、密码模式:第一方单页应用与第一方原生App
4、客户端模式:没有用户参与的,完全信任的服务器端服务
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了