1、Nacus 服务搭建及使用
2、Nacos 配置中心
3、Nacos 服务注册与发现之OpenFeign服务间调用
4、Spring Security & Oauth2 认证授权
5、网关(Gateway)的搭建及使用
6、网关(Gateway自定义断言和过滤器)
微服务开发这么流行,安全认证非常重要,Spring Security + Oauth2 作为一个 认证授权的框架,不可缺。
在此简单说明并且做学习笔记。
开发工具:IDEA
授权码是相对比较安全的模式,需要用户首先通过接口获取一个 授权码,然后再用 client_id 和 client_secret 加上 获取到的授权码进行认证。
简化模式多用于单一应用,没有服务端的第三方单页面应用,因为没有服务器端就无法接受授权码。
密码模式一般用于我们自己开发的,第一方原生APP或者是第一方面页面应用;因为会暴露密码出来,所以最好client端是自己开发的或者我们比较信任的
我们对client足够信任的情况下使用客户端模式,客户端本身提供的 client_id 和 client_secret 进行认证
因为使用的是 Spring security + Oauth2 进行认证与授权的,所以要添加 Spring security 和 Oauth2 的依赖包;
也是一个web项目,所以需要添加 boot 的 web 包。
版本:版本是 2.x
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
ClientAccessDeniedHandler clientAccessDeniedHandler;
@Autowired
UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider;
/**
* 配置 Http 请求 安全策略
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //关闭 csrf 保护
.authorizeRequests().antMatchers(HttpMethod.OPTIONS,"/login","/oauth/**").permitAll() //配置哪些路径可以访问
.anyRequest().authenticated() //配置所有的请求都要权限验证
.and()
.formLogin().loginPage("/login.html") //自定义登录页面
.and()
.exceptionHandling().accessDeniedHandler(clientAccessDeniedHandler); //配置访问受限时的情况
//super.configure(http);
}
/**
* 配置一个 provider 进行用户信息的获取和验证操作
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(usernamePasswordAuthenticationProvider);
}
/**
* 初始化一个 provider 提供校验操作
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(usernamePasswordAuthenticationProvider);
}
/**
* 设置密码加密方式
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
@Component
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
/**
* 这里使用 UserDetailService 进行加载用户信息
*/
@Autowired
WeshUserDetailService userDetailService;
/**
* 认证方法
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String principle = authentication.getPrincipal().toString(); //username
String credential = authentication.getCredentials().toString(); //password
UserDetails userDetails = userDetailService.loadUserByUsername(principle);
return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(),userDetails.getAuthorities());
}
/**
* 判断是不是使用此 provider 进行认证操作,返回true 则在认证的时候执行上面的 authenticate() 方法
* @param aClass
* @return
*/
@Override
public boolean supports(Class<?> aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
@Component
public class WeshUserDetailService implements UserDetailsService {
/**
* 框架会掉此方法加载用户信息
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if("100001".equals(username)){
User user = new User("100001","admin001", Collections.emptyList());
return user;
}
return null;
}
}
@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter{
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private WeshClientDetailsService clientDetailsService;
@Autowired
private TokenStore tokenStore;
/**
* 配置登录用户信息服务
* @param configurer
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
//配置数据库获取客户信息
configurer.withClientDetails(clientDetailsService);
//super.configure(clients);
}
/**
* 配置令牌
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager) //需要提供一个认证管理器
.accessTokenConverter(jwtAccessTokenConverter) //jwt 进行密码转换加密
.tokenServices(tokenServices()) //token 管理服务
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
//super.configure(endpoints);
}
/**
* 配置终端(api)
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()") //token/token_key
.checkTokenAccess("permitAll()") //check_token
.allowFormAuthenticationForClients(); //允许表单认证提交,不放开,登录页面表单提交不了
//super.configure(security);
}
@Bean
public AuthorizationServerTokenServices tokenServices(){
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setClientDetailsService(clientDetailsService); //配置生成token获取用户信息服务
tokenServices.setTokenStore(tokenStore); //token 存储服务
tokenServices.setReuseRefreshToken(true);
tokenServices.setAccessTokenValiditySeconds(7200);
tokenServices.setReuseRefreshToken(false); // 设置为 true, 则没有 refresh_token 在返回体中
tokenServices.setSupportRefreshToken(true); //支持 refresh token
tokenServices.setRefreshTokenValiditySeconds(259200); //刷新令牌3天有效期
//设置token的加密方式为 JWT
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
tokenServices.setTokenEnhancer(tokenEnhancerChain);
return tokenServices;
}
}
@Service
public class WeshClientDetailsService implements ClientDetailsService {
/**
* 实现具体如何读取用户信息
* 项目中可以用从数据库中查询
* @param s
* @return
* @throws ClientRegistrationException
*/
@Override
public ClientDetails loadClientByClientId(String s) throws ClientRegistrationException {
Client client = new Client();
if("100001".equals(clientId)){ //授权码模式需要提供跳转地址 100001 作为授权码账号
client.setClientId("100001");
client.setClientSecret(new BCryptPasswordEncoder().encode("admin001"));
client.getScope().add("all"); //权限范围是all,所有
client.getAuthorizedGrantTypes().add("authorization_code");
client.getRegisteredRedirectUri().add("http://www.baidu.com");
}
if("100002".equals(clientId)){ // 100002 作为 password 模式跳转
client.setClientId("100002");
client.setClientSecret(new BCryptPasswordEncoder().encode("admin001"));
client.getScope().add("all"); //权限范围是all,所有
}
return client;
}
}
/**
* Token 配置 jwt 加密
*/
@Configuration
public class TokenConfig {
/**
* jwt 配置的一个 签名
*/
private final String SIGNING_KEY = "*****";
/**
* 初始化 token store 来存储token
* @return
*/
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* jwt 加密token配置
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
tokenConverter.setSigningKey(SIGNING_KEY);
return tokenConverter;
}
}
http://localhost:9999/uaa/oauth/authorize?client_id=client01&response_type=code&scope=all&redirect_uri=http://www.baidu.com (GET)
参数说明:
client_id: 用户id
client_secret: 用户密码
response_type: 请求认证方式,值为 ‘code’
点击Authorize之后,会跳转到 redirect url,并传参授权码
http://localhost:9999/uaa/oauth/authorize?client_id=client01&response_type=token&scope=all&redirect_uri=http://www.baidu.com (GET)
注意:response_type是 token
调用授权接口,和 授权码模式同一个接口,不过 response_type 是 token
http://localhost:9999/uaa/oauth/token(POST)
参数:
client_id=client01,
client_secret=client01,
grant_type=password,
username=zhangsan,
password=123
http://localhost:9999/uaa/oauth/token
参数
client_id=client01
client_secret=client01
grant_type=client_credentials
spring security + oauth2进行了简单的搭建和记录,大致理解了 spring security oauth2 框架,项目初始搭建完成,不过此框架还有很多细节部分需要再深究。
自己总结的往往影响比较深刻,哪怕忘记了,看自己的文章也更熟悉。
而且我的文章目录清晰,可以迅速定位到需要看的那部分。
项目源码
有详细注释。
如果令尊看到这里了,希望不要浪费您的宝贵的实际,这篇简短的文章能帮助到令尊!!!