鉴权篇
----------------------------------------------------------------------------
认证解决你是谁的问题,授权解决你能干什么的问题。
----------------------------------------------------------------------------
SpringSecurity中的授权:
继承WebSecurityConfigurerAdapter(加@EnableWebSecurity、@Configuration注解)实现configure方法,该方法中定义匿名访问的url,antMatchers(url,url,...).permitAll()。简单角色访问,写死在代码中.antMatchers(url).hasRole("ADMIN")访问这个url需要ROLE_ADMIN角色。具体做法是实现UserDetailService接口,在loadUserByUsername中设置该角色,如下:
return new User(username, passwordEncoder.encode("123456"),AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN"));
授权表达式除了hasRole还有denyAll(拒绝所有)、hasAnyRole(role1,role2)有任何一个即可。
RBAC(Role-Based Access Control)数据模型。五表,用户、角色、资源、用户-角色、资源-角色。实现如下:
自定义一个接口和实现类,接口中方法参数request,authentication,返回值布尔类型。
实现类中获取当前用户信息,用户信息中包含了所有权限,用户每次点击一个url,都会进入该方法,判断这个url是否在用户的权限中,是返回true,false则无权。判断语句使用antPathMatcher.match(url, request.getRequestURI())
怎样配置使访问url进入自己的方法,如下:
anyRequest().access("@rbacService.hasPermission(request,authentication)");
----------------------------------------------------------------------------
SpringSecurity启动:
引入依赖spring-boot-starter-security,无需配置,直接启动,后台会打印出密码,默认的用户名是user,有表单登录和弹框登录两种
自定义认证:
实现UserDetailsService接口,实现loadUserByUsername方法,返回UserDetails,在该方法里面new User(用户名,密码加密,true,true,true,true,权限)。4个布尔依次是:
用户已失效;用户帐号已过期;用户凭证已过期;用户帐号已被锁定
登录页面/authentication/form访问,默认使用username,password作为输入框的名称
----------------------------------------------------------------------------
验证码处理流程为:生成验证码->放在Session中->验证->清空Session
----------------------------------------------------------------------------
OAuth协议中的角色:服务提供商、资源所有者、第三方应用。
服务提供商(Provider):逻辑上分为认证服务器和资源服务器,其实可以写在一个应用上。
认证服务器(Authorization Server):认证用户的身份、产生令牌。
资源服务器(Resource Server):保存用户的资源、验证令牌。
资源所有者(Resource Owner):拥有资源的人,比如微信用户,QQ用户等,拥有自己的照片、文件等资源。
第三方应用(Client):我们自己开发的应用,访问用户在微信、QQ上的数据,提供功能。
----------------------------------------------------------------------------
OAuth协议的流程:
用户访问第三方应用,第三方应用请求资源所有者授权(用户就是资源所有者)。
资源所有者同意授权。
第三方应用访问认证服务器申请令牌。
认证服务器验证用户确实授权了,就发放令牌给第三方应用。
第三方应用拿到了令牌,带着令牌访问资源服务器申请获取资源。
资源服务器验证令牌无误后,把资源开放给第三方应用。
----------------------------------------------------------------------------
OAuth协议中4种授权模式:
授权码模式(authorization code)
简化模式(implicit)使用少
密码模式(resource owner password credentials)
客户端模式(client credentials)使用少
----------------------------------------------------------------------------
授权码模式流程:
过程:用户访问第三方应用,第三方应用将用户导向认证服务器授权,授权后,认证服务器返回授权码,第三方应用携带授权码去申请令牌。
1)第三方应用需要用户授权,会将用户导向认证服务器。
2)用户同意授权(用户授权的动作在认证服务器上完成)。
3)认证服务器会将用户重新导回到第三方应用上去,同时返回一个授权码,导到哪个地址上是第三方应用和认证服务器提前商量好的。
4)第三方应用在收到授权码以后携带授权码去认证服务器申请令牌(发出申请动作在第三方应用上完成的,用户不可见)。
5)认证服务器核对第三方应用携带的授权码是不是第三步发的,如果是则向第三方应用发放令牌。
----------------------------------------------------------------------------
授权码模式的特点:
1)用户同意的动作是在认证服务器上完成的,其他3种模式是在第三方应用上完成的。
2)用户同意授权,返回第三方时携带的不是令牌,而是授权码,第三方拿着授权码再去换取令牌。
----------------------------------------------------------------------------
授权码模式的好处:
用户授权时在认证服务器保存授权码,第三方应用请求令牌时携带该授权码进行对比,更加安全。如果用户同意的动作在第三方应用上完成,然后第三方应用拿着用户同意的凭证信息去认证服务器申请令牌的话,不够安全,因为第三方应用可以伪造凭证信息。认证服务器无法确定用户是否真正的授权了。
----------------------------------------------------------------------------
Cookie-Session与Token方式:
浏览器直接访问应用服务器的时候适合使用Cookie-Session方式,因为浏览器默认处理了Cookie信息。其实使用APP或者前后端分离也可以使用Cookie-Session方式,理论上只要发送HTTP请求的时候能够处理Cookie就可以,但是这样做会出现问题:开发繁琐。有些不支持Cookie,微信小程序不支持Cookie。
----------------------------------------------------------------------------
SpringSecurityOAuth2默认实现OAuth2:
引入starter-security和spring-security-oauth2-autoconfigure依赖
application.properties中配置client-id、client-secret、redirect-uri,
自定义认证服务器类添注解如下,空类不用写方法
@Configuration、@EnableAuthorizationServer
自定义资源服务器类添注解如下,空类不用写方法
@Configuration、@EnableResourceServer
获取的token可以放在内存或者redis中:endpoints.tokenStore(tokenStore)
----------------------------------------------------------------------------
授权码模式下,请求授权码所需参数:
response_type:------必填,值是固定的常量code
client_id:-------------必填,值是应用ID,配置文件中定义的MyProject
redirect_uri:---------非必填,值是回调地址
scope:----------------非必填,值是范围
state:-----------------非必填,值是状态
----------------------------------------------------------------------------
授权码模式下,请求Token所需参数:
grant_type:---------必填,值是固定的常量authorization_code
code:----------------必填,值是上面一步请求返回的授权码
redirect_uri:--------必填,值是回调地址
client_id:------------必填,值是应用ID,配置文件中定义的MyProject
----------------------------------------------------------------------------
密码模式下,请求Token所需参数:
grant_type:---------必填,值是固定的常量password
username:----------必填,值是用户名
password:-----------必填,值是用户密码
scope:---------------非必填,范围
----------------------------------------------------------------------------
JWT基础:Json Web Token
自包含。JWT中包含着Token有意义的信息,拿到Token,解析后就能知道里面包含的信息是什么,而Spring默认生成的Token是UUID,没有任何有意义的信息。它的信息需要根据这个Token去Redis中读取。
密签。发出去的令牌不包含密码等敏感信息,使用指定的秘钥签名。
可扩展。包含的信息可以根据业务需求自己定义。
----------------------------------------------------------------------------
JWT用法:
用法与上面SpringSecurityOAuth2默认实现OAuth2基本一样,引入一样的依赖。在认证服务配置中设置endpoints.accessTokenConverter(jwtAccessTokenConverter),在生成JwtAccessTokenConverter时设置秘钥,jwt存redis最好。
JWT的鉴权,给用户赋值权限永远在UserDetailService中,在jwt中可以解析出来,使用JWTUtils(直接注入)。同样拿点击的url也权限中的url对比
前端怎么解析,vue直接引入jwt的依赖包,更简单
----------------------------------------------------------------------------
JWT单点登录:
所谓单点登录就是有多个应用部署在不同的服务器上,只需登录一次就可以互相访问不同服务器上的资源。
当请求发给应用A,A向认证服务器请求授权,把用户引导到认证服务器上。用户在认证服务器上完成认证并授权。然后返回给应用A一个授权码,应用A携带授权码到认证服务器请求令牌,认证服务器返回应用A一个JWT,应用A解析JWT里面的信息,完成登录。这是一个标准的OAuth2的授权码流程。走完认证流程后,给出去的JWT实际上里面包含的就是当前用户在认证服务器上登录以后用户的认证信息,应用A解析JWT后,自己生成一个经过认证的Authentication放到它的SpringSecurity和SecurityContext里面。访问应用B的时候,同样引导用户去认证服务器请求授权(不需要登录),用户授权可以用登录的信息去访问应用B,后面同样是授权码流程,返回JWT给应用B。两个应用返回不同的JWT,但是解析出的信息是一样的。
----------------------------------------------------------------------------
jwt单点登录的实现:
独立的认证服务器,UserDetailService认证授权,配置两个需要认证的服务,指定client_id,授权模式即授权码模式或密码模式,重定向uri,这个写每个应用的登录地址即可。指定秘钥生成JwtAccessTokenConverter。可以设置跳过授权.autoApprove(true);
独立的应用服务器,需要在配置文件中配置认证服务的ip端口/oauth/token和/oauth/token_key。主启动类上需要添加@EnableOAuth2Sso注解
----------------------------------------------------------------------------
session单点登录:巨简单
引入spring-session-data-redis、spring-session依赖。application.properties中配redis地址、端口和spring.session.store-type = redis
注意:放到redis中的实体类要实现序列化接口
----------------------------------------------------------------------------