①权限管理:一般指根据系统设置的安全策略或者安全规则,用户可以访问而且只能访问自己被授权的资源,不多不少。权限管理几乎出现在任何系统里面,只要有用户和密码的系统。
②权限管理分类:
访问权限:管理员有增删改查权限,普通用户只有查询权限。
数据权限:管理员可以看到所有员工信息,但是员工只能看到自己的信息。
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和密码,看其是否与系统中存储的该用户的用户名和密码一致,来判断用户身份是否正确。例如:密码登录,手机短信验证、三方授权等。
Apache Shiro是一个强大且易用的Java安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
1.简单的身份验证,支持多种数据源
2.对角色的简单授权,支持细粒度的授权(方法)
3.支持一级缓存,以提升应用程序的性能
4.内置基于POJO的企业会话管理,适用于web及非web环境
5.非常简单的API加密
6.不跟任何框架绑定,可以独立运行
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如爬虫、机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者。
SecurityManager:安全管理器,即所有与安全有关的操作都会与SecurityManager交互,且它管理着所有Subject;可以看出它是shiro的核心, SecurityManager相当于spring mvc中的dispatcherServlet前端控制器。
Realm:域,shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
1)通过ini配置文件创建securityManager
2)调用subject.login方法主体提交认证,提交的token
3)securityManager进行认证,securityManager最终由ModularRealmAuthenticator进行认证。
4)ModularRealmAuthenticator调用IniRealm(给realm传入token) 去ini配置文件中查询用户信息
5)IniRealm根据输入的token(UsernamePasswordToken)从 shiro.ini查询用户信息,根据账号查询用户信息(账号和密码)
如果查询到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码)
如果查询不到,就给ModularRealmAuthenticator返回null
6)ModularRealmAuthenticator接收IniRealm返回Authentication认证信息;
如果返回的认证信息是null,ModularRealmAuthenticator抛出异常(org.apache.shiro.authc.UnknownAccountException)
如果返回的认证信息不是null(说明inirealm找到了用户),对IniRealm返回用户密码 (在ini文件中存在)和 token中的密码 进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)
1)对subject进行授权,调用方法isPermitted("permission串")
2)SecurityManager执行授权,通过ModularRealmAuthorizer执行授权
3)ModularRealmAuthorizer执行realm(自定义的Realm)从数据库查询权限数据
调用realm的授权方法:doGetAuthorizationInfo
4)realm从数据库查询权限数据,返回ModularRealmAuthorizer
5)ModularRealmAuthorizer调用PermissionResolver进行权限串比对
6)如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。
1.@RequiresAuthentication : 表示当前Subject已经通过login进行了身份验证;即 Subject.isAuthenticated() 返回 true
2.@RequiresUser : 表示当前Subject 已经身份验证或者通过记住我登录的
3.@RequiresGuest : 表示当前Subject没有身份验证或通过记住我登陆过,即是游客身份
4.@RequiresRoles(value = { “admin”, “user” }, logical = Logical.AND) : 表示当前 Subject 需要角色 admin和user
5.@RequiresPermissions(value = { “user:a”, “user:b” }, logical = Logical.OR) : 表示当前 Subject 需要权限 user:a 或 user:b
Spring Security在架构上将认证与授权分离,并提供了扩展点。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成 ,并配备了流行的安全算法实现捆绑在一起。
1.客户端发起一个请求,进入 Security 过滤器链。
2.当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。
3.当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。
4.当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。
1)Shiro比Spring Security更容易使用,也就是实现上简单一些,同时基本的授权认证Shiro也基本够用
2)Spring Security社区支持度更高,Spring社区的亲儿子,支持力度和更新维护上有优势,同时和Spring这一套的结合较好。
3)Shiro功能强大、且 简单、灵活。是Apache 下的项目比较可靠,且不跟任何的框架或者容器绑定,可以独立运行。
SpringSecurity中提供了专业的方式来解决预检请求所面临的问题:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
// 开启跨域配置
.cors()
.configurationSource(corsConfigurationSource())
.and()
.csrf().disable();
}
CorsConfigurationSource corsConfigurationSource() {
// 提供CorsConfiguration实例,并配置跨域信息
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("*"));
corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8081"));
corsConfiguration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}
cors()方法开启了对CorsConfigurer的配置,其最重要的方法就是configure方法:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
// 开启跨域配置
.cors()
.configurationSource(corsConfigurationSource())
.and()
.csrf().disable();
}
CorsConfigurationSource corsConfigurationSource() {
// 提供CorsConfiguration实例,并配置跨域信息
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("*"));
corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8081"));
corsConfiguration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}
拿到CorsFilter之后,调用http.addFilter方法将其添加到spring security过滤器链中,在过滤器链构建之前,会先对所有的过滤器进行排序,排序的依据在FilterOrderRegistration中已经定义好了:
FilterOrderRegistration() {
Step order = new Step(INITIAL_ORDER, ORDER_STEP);
put(ChannelProcessingFilter.class, order.next());
order.next(); // gh-8105
put(WebAsyncManagerIntegrationFilter.class, order.next());
put(SecurityContextPersistenceFilter.class, order.next());
put(HeaderWriterFilter.class, order.next());
put(CorsFilter.class, order.next());
put(CsrfFilter.class, order.next());
put(LogoutFilter.class, order.next());
// ...
}
可以看到,CorsFilter的位置在HeaderWriterFilter之后,在CsrfFilter之前,这个时候还没到认证过滤器。Spring security根据开发者提供的CorsConfigurationSource对象构建出一个CorsFilter,并将该过滤器置于认证过滤器之前。
Spring Security 提供了多种加密算法的实现,开箱即用,非常方便。这些加密算法实现类的父类是 PasswordEncoder ,如果你想要自己实现一个加密算法的话,也需要继承 PasswordEncoder。
PasswordEncoder 接口一共也就 3 个必须实现的方法。
public interface PasswordEncoder {
// 加密也就是对原始密码进行编码
String encode(CharSequence var1);
// 比对原始密码和数据库中保存的密码
boolean matches(CharSequence var1, String var2);
// 判断加密密码是否需要再次进行加密,默认返回 false
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
官方推荐使用基于 bcrypt 强哈希函数的加密算法实现类。
推荐的做法是通过 DelegatingPasswordEncoder 兼容多种不同的密码加密方案,以适应不同的业务需求。
DelegatingPasswordEncoder 其实就是一个代理类,并非是一种全新的加密算法,它做的事情就是代理上面提到的加密算法实现类。在 Spring Security 5.0 之后,默认就是基于 DelegatingPasswordEncoder 进行密码加密的。
● permitAll() :无条件允许任何形式访问,不管你登录还是没有登录。
● anonymous() :允许匿名访问,也就是没有登录才可以访问。
● denyAll() :无条件决绝任何形式的访问。
● authenticated():只允许已认证的用户访问。
● fullyAuthenticated() :只允许已经登录或者通过 remember-me 登录的用户访问。
● hasRole(String) : 只允许指定的角色访问。
● hasAnyRole(String) : 指定一个或者多个角色,满足其一的用户即可访问。
● hasAuthority(String) :只允许具有指定权限的用户访问
● hasAnyAuthority(String) :指定一个或者多个权限,满足其一的用户即可访问。
● hasIpAddress(String) : 只允许指定 ip 的用户访问。
单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案 之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
如果禁用cookie可以使用url中带参数,把token传递给服务端。当然此方法涉及安全性问题,其实在cookie中保存token同样存在安全性问题。推荐使用sso框架CAS实现单点登录。
以登录天猫为例进行说明:
1)当⽤户第⼀次访问淘宝的时候,因为还没有登录,会被引导到认证中⼼进⾏登录。
2)根据⽤户提供的登录信息,认证系统进⾏身份验证,如果通过,则登录成功,并返回给⽤户⼀个认证的凭据(JWT token)。
3)当⽤户访问天猫时,就会将这个 JWT token 带上,作为⾃⼰认证的凭据。
4)应⽤系统接收到请求后会把 JWT token 送到认证中⼼进⾏校验。
5)如果通过校验,⽤户就可以在不⽤再次登录的情况下访问天猫了。
1)代理登录(agent):用于无法改造的旧系统;
2)令牌环(token):通过Cookie共享令牌环的方式传递当前用户信息,实现SSO,存在跨域问题;
3)身份票据(ticket):除了增加一台信任验证服务器,完全满足了存储信任,验证信任,作用范围和安全性的问题,也是适用最广的webSSO实现方式
CAS框架:CAS(Central Authentication Service,即:统一认证服务)是实现SSO单点登录的框架。CAS分为两部分,CAS Server和CAS Client。
CAS Server用来负责用户的认证工作,就像是把第一次登录用户的一个标识存在这里,以便此用户在其他系统登录时验证其需不需要再次登录。
CAS Client就是我们自己开发的应用程序,需要接入CAS Server端。当用户访问我们的应用时,首先需要重定向到CAS Server端进行验证,要是原来登陆过,就免去登录,重定向到下游系统,否则进行用户名密码登陆操作。
Ticket Granting ticket (TGT) :可以认为是CAS Server根据用户名密码生成的一张票,存在Server端
Ticket-granting cookie (TGC) :其实就是一个Cookie,存放用户身份信息,由Server发给Client端
Service ticket (ST) :由TGT生成的一次性票据,用于验证,只能用一次。相当于Server发给Client一张票,然后Client拿着这个票再来找Server验证,看看是不是Server签发的。
1)用户访问网站,第一次来,重定向到 CAS Server,发现没有cookie,所以再重定向到CAS Server端的登录页面,并且URL带有网站地址,便于认证成功后跳转,形如 http ?/cas-server:8100/login?service=http ?/localhost:8081
注意:service后面这个地址就是登录成功后要重定向的下游系统URL。
2)在登陆页面输入用户名密码认证,认证成功后cas-server生成TGT,再用TGT生成一个ST。 然后再第三次重定向并返回ST和cookie(TGC)到浏览器
3)浏览器带着ST再访问想要访问的地址:
http ?/localhost:8081/?ticket=ST-25939-sqbDVZcuSvrvBC6MQlg5
注意:ticket后面那一串就是ST
4)浏览器的服务器收到ST后再去cas-server验证一下是否为自己签发的,验证通过后就会显示页面信息,也就是重定向到第1步service后面的那个URL
首次登陆完毕。
5)再登陆另一个接入CAS的网站,重定向到CAS Server,server判断是第一次来(但是此时有TGC,也就是cookie,所以不用去登陆页面了),但此时没有ST,去cas-server申请一个于是重定向到cas-server,形如:http: //cas-server:8100/login?service=http ?/localhost:8082 && TGC(cookie) (传目标地址和cookie)
6)cas-server生成了ST后重定向给浏览器http ?/localhost:8082/?ticket=ST-25939-sqfsafgefesaedswqqw5-xxxx
7)浏览器的服务器收到ST后再去cas-server验证一下是否为自己签发的,验证通过后就会显示页面信息(同第4步)
Token的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。
当用户第一次登录后,服务器生成一个token并将此token返回给客户端,以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。
简单Token的组成;uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token的前几位以哈希算法压缩成的一定长度的十六进制字符串。为防止token泄露)。
OAuth 是一个行业的标准授权协议,主要用来授权第三方应用获取有限的权限。实际上它就是一种授权机制,最终目的是为第三方应用颁发一个有时效性令牌 token,使得第三方应用能够通过该令牌获取相关的资源。
OAuth 2.0 比较常用的场景就是第三方登录,当你的网站接入了第三方登录时一般就是使用的 OAuth 2.0 协议。
现在OAuth 2.0也常见于支付场景(微信支付、支付宝支付)和开发平台(微信开放平台、阿里开放平台等等)。
Access Token 是在 Oauth2.0 协议中,客户端访问资源服务器时需要带上的令牌(其实就是一段全局唯一的随机字符串)。拥有这个令牌代表着得到用户的授权。令牌里面包含哪个用户 在什么时候 授权给哪个app去做什么事情。当然这些信息是不能直接从Access Token 看出来的,而是存在平台方的数据库中,平台可以用Access Token 作为 key 去查询出这些信息,然后验证调用方是否有权限。
Refresh Token是专用于刷新 Access Token 的 token。如果没有Refresh Token,也可以刷新 Access Token,但每次刷新都要用户输入登录用户名与密码。有了 Refresh Token,客户端直接用Refresh Token 去更新Access Token,无需用户进行额外的操作。
Json Web Token (JWT)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。该token被设计为紧凑且安全,特别适用于分布式站点单点登录场景。
JWT由头部(header)、载荷(payload)、签证(signature) 三部分组成。