赞
踩
Session是存储在服务器端的,当浏览器第一次请求Web服务器,服务器会产生一个Session存放在服务器里(可持久化到数据库中),然后通过响应头的方式将SessionID返回给浏览器写入到Cookie中,浏览器下次请求就会将SessiondID以Cookie形式传递给服务器端,服务器端获取SessionID后再去寻找对应的Session。如果找到了则代表用户不是第一次访问,也就记住了用户。
但需要注意的是,若服务器做了负载均衡,用户的下一次请求可能会被定向到其它服务器节点,若那台节点上没有用户的Session信息,就会导致会话验证失败。所以Session默认机制下是不适合分布式部署的。
OAuth 2.0为用户和应用定义了如下角色:
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pEnS9c7R-1666258112257)(C:\Users\zjy\Documents\MD\授权中心(AuthorizationServerConfigurerAdapter).png)]
这里我们先继承该类,重写和内容丰富将在下面的章节完成
package com.zjy.security.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { public AuthorizationServerConfig() { super(); } //客户端详情配置,可以写死也可以通过数据库查询 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { } // 配置Token访问端点(url)和Token服务(生成Token) @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { } //Token端点安全约束 @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { } }
该配置通过重写授权中心中方法完成
//客户端详情配置,可以写死也可以通过数据库查询
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("Clinet01")
.secret(new BCryptPasswordEncoder().encode("password"))//秘钥
.resourceIds("resource01")
.authorizedGrantTypes() //客户端默认权限类型
.scopes("all") //授权范围
.authorities() //可缺省,看需求
.autoApprove(false) //是否不跳转到授权页面
.redirectUris("/index");//验证回调地址
}
clientId:(必须的)用来标识客户的Id。
secret:(需要值得信任的客户端)客户端安全码,如果有的话。
scope:用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。
authorizedGrantTypes:此客户端可以使用的授权类型,默认为空。
authorization_code — 授权码模式(即先登录获取code,再获取token)
password — 密码模式(将用户名,密码传过去,直接获取token)
client_credentials — 客户端模式(无用户,用户向客户端注册,然后客户端以自己的名义向’服务端’获取资源)
implicit — 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash)
refresh_token — 刷新access_token
(这里的关键字我在源代码并没有找到,希望能有大佬能指点一下这些关键字(大概率是枚举)的位置)
authorities:此客户端可以使用的权限(基于Spring Security authorities)
这个Bean的创建根本上是为了下一步配置Token访问节点的需要。
自己可以创建 AuthorizationServerTokenServices 这个接口的实现,则需要继承 DefaultTokenServices 这个类。而持久化令牌是委托一个 TokenStore 接口来实现。
package com.zjy.security.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; @Configuration public class TokenConfig { @Bean public TokenStore tokenStore(){ return new InMemoryTokenStore(); } }
在授权中心创建Token管理服务(AuthorizationServerTokenServices)的Bean
@Resource
private TokenStore tokenStore;
@Resource
private ClientDetailsService clientDetailsService;
@Bean//令牌管理服务
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(clientDetailsService);
services.setSupportRefreshToken(true);
services.setTokenStore(tokenStore);
services.setAccessTokenValiditySeconds(3600);
services.setRefreshTokenValiditySeconds(259200);
return services;
}
哪来的ClientDetailsService Bean呢?其实这里@EnableAuthorizationServer注解中会引入配置类ClientDetailsServiceConfiguration,该配置类会创建一个默认的ClientDetailsService!
这里我们如果想要自定义一个ClientDetailsService的Bean,需要在类里加上@Primary把该类当成是主类,否则会导致循环。样例如下:
@Service
@Primary
public class CustomClientDetailService implements ClientDetailsService {
@Resource
private PasswordEncoder passwordEncoder;
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
BaseClientDetails clientDetails = new BaseClientDetails(clientId, "resource_id", "all,select", "authorization_code,client_credentials,refresh_token,password", "aut", "http://www.baidu.com");
clientDetails.setClientSecret(passwordEncoder.encode("secret_" + clientId));
return clientDetails;
}
}
这里我们选择"authorization_code" 授权码类型模式。同样是在授权中心,配置Bean,当场注入
@Resource
private AuthorizationCodeServices authorizationCodeServices;
@Bean
public AuthorizationCodeServices authorizationCodeServices(){
return new InMemoryAuthorizationCodeServices();
}
@Resource
private AuthorizationServerTokenServices tokenServices;
代码如下
package com.zjy.security.service; import com.zjy.security.mapper.UserMapper; import com.zjy.security.pojo.MyUser; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; @Service("userDetailsService") public class MyUserDetailsService implements UserDetailsService { @Resource private UserMapper usersMapper; AuthenticationManager authenticationManager(){ return new OAuth2AuthenticationManager(); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { MyUser user=usersMapper.getUserByName(username); if(user == null) { throw new UsernameNotFoundException("用户名不存在!"); } System.out.println(user); //角色需要前缀“ROLE_”,权限不需要,角色和权限全部写进一个字符串,用逗号分隔 List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_vip1,ROLE_vip2"); //********************************** //如果权限和角色也写进数据库呢? //查出角色和权限后,遍历进行如下写入: //auths.add(new SimpleGrantedAuthority(" ")); //*********************************** return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), auths); } }
授权中心注入:
@Resource
private MyUserDetailsService userDetailsService;
接下来就是正题:配置AuthorizationServerEndpoints
// 配置Token访问端点(url)和Token服务(生成Token)
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(new OAuth2AuthenticationManager())
.authorizationCodeServices(authorizationCodeServices)
.tokenServices(tokenServices)
.userDetailsService(userDetailsService)
.allowedTokenEndpointRequestMethods(HttpMethod.GET)
//.pathMapping("/oauth/authorize","/myAuth/authorize")
;
}
AuthorizationServerEndpointsConfigurer 通过设定以下属性决定支持的授权类型(Grant Types):
AuthorizationServerEndpointsConfigurer 这个配置对象有一个叫做 pathMapping() 的方法用来配置端点URL链 接,它有两个参数:
以上的参数都将以 “/” 字符为开始的字符串,框架的默认URL链接如下列表,可以作为这个 pathMapping() 方法的 第一个参数:
需要注意的是授权端点这个URL应该被Spring Security保护起来只供授权用户访问.
//Token端点安全约束
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")//具体权限写法应该同security
.allowFormAuthenticationForClients();//申请Token
}
package com.zjy.security.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import javax.annotation.Resource; import javax.sql.DataSource; @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/static/index.html").permitAll() .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3") ; //配置认证 http .formLogin() .loginPage("/index") // 配置哪个 url 为登录页面 .loginProcessingUrl("/login") // 设置哪个是登录的 url。 .successForwardUrl("/success") // 登录成功之后跳转到哪个 url .failureForwardUrl("/fail") .and() .logout().deleteCookies().invalidateHttpSession(true) .and() .rememberMe().tokenValiditySeconds(60*60) ; } }
尝试加入下述配置
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>3.0.2</version>
</dependency>
至此,授权中心配置完毕。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wuOEetCn-1666258112257)(C:\Users\zjy\Documents\MD\OAuth\授权码模式.png)]
由于配置时我选择的获取令牌请求为get(方便测试),这里我们通过在浏览器直接键入URL发起请求。
(我这里redirect_uri=www.baidu.com是为了与授权中心配置的redirectUris一致)
http://localhost:8080/oauth/authorize?client_id=Clinet01&response_type=code&scope=all&redirect_uri=www.baidu.com
转到,跳转到页面:
http://localhost:8080/oauth/www.baidu.com?code=U58iIN
得到授权码:U58iIN
http://localhost:8080/oauth/token?client_id=Clinet01&client_secret=password&grant_type=authorization_code&code=U58iIN&redirect_uri=www.baidu.com
页面显示:
{"access_token":"eb8f91ea-3c19-4a48-8206-7ecbec940978","token_type":"bearer","expires_in":3599,"scope":"all"}
请求:
http://localhost:8080/oauth/authorize?client_id=Clinet01&response_type=token&scope=all&redirect_uri=www.baidu.com
页面显示:
http://localhost:8080/oauth/www.baidu.com#access_token=e1df8f0b-e430-429b-a9d9-4405bab301f0&token_type=bearer&expires_in=3599
直接获取了令牌
http://localhost:8080/oauth/token?client_id=Clinet01&client_secret=password&grant_type=password&username=111&password=111&redirect_uri=www.baidu.com
http://localhost:8080/oauth/token?client_id=Clinet01&client_secret=password&grant_type=client_credentials&redirect_uri=www.baidu.com
基于事先写过的一个常规三层架构provider,将其改造成符合OAuth的资源服务器。实际上并不需要更改其中原有代码,只需要加入两个配置类
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
package com.zjy.dataprovider8000.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; @Configuration public class TokenResolveServiceConfig { @Bean(name="tokenServices") public RemoteTokenServices tokenServices(){ RemoteTokenServices tokenServices=new RemoteTokenServices(); tokenServices.setClientSecret("password"); tokenServices.setCheckTokenEndpointUrl("http://localhost:9500"+"/oauth/check_token"); tokenServices.setClientId("Clinet01"); return tokenServices; } }
package com.zjy.dataprovider8000.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import javax.annotation.Resource; @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Resource RemoteTokenServices tokenServices; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources .resourceId("resource01")//这里要和授权中心配置的资源名相对应 .tokenServices(tokenServices)//验证令牌服务 .stateless(true); } @Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers( "/swagger-ui.html", "/swagger-resources/**", "/v2/api-docs", "/oauth/check_token").permitAll() .antMatchers("/**").access("#oauth2.hasScope('all')") .and() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//关闭session } }
按照oauth2.0协议要求,请求资源需要在Header里携带token,具体如下:
token的参数名称为:Authorization,值为:Bearer [token值]
手动获取token后用postman发起请求,通过,验证成功。
这里如果报401:nobody之类的错误,优先检查自己资源服务器和授权中心写死的验证信息是否有误,笔者这里单词写错导致第一次测试不通过
如果需要更细粒度的权限控制,在授权中心可以给令牌配置不同权限,在资源服务器处用antMatchers()拦截。
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于 在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公 钥/私钥对来签名,防止被篡改。
官网:https://jwt.io/
标准:https://tools.ietf.org/html/rfc7519
1)jwt基于json,非常方便解析。
2)可以在令牌中自定义丰富的内容,易扩展。
3)通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
4)资源服务使用JWT可不依赖认证服务即可完成授权。
1)JWT令牌较长,占存储空间比较大。
通过学习JWT令牌结构为自定义jwt令牌打好基础。
JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz
{
"alg": "HS256",
"typ": "JWT"
}
将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比 如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
一个例子:
{
"sub": "1234567890",
"name": "456",
"admin": true
}
第三部分是签名,此部分用于防止jwt内容被篡改。
这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明 签名算法进行签名。
一个例子:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。