当前位置:   article > 正文

SpringCloud实操(二)搭建OAuth2认证授权服务_springcloud鉴权 oauth2

springcloud鉴权 oauth2

1. 概念部分

1.1 为什么需要做一个单独的认证授权服务

为了保证服务对外的安全性,往往都会在服务接口采用权限校验机制,为了防止客户端在发起请求中途被篡改数据等安全方面的考虑,还会有一些签名校验的机制。

在分布式微服务架构的系统中,我们把原本复杂的系统业务拆分成了若干个独立的微服务应用,我们不得不在每个微服务中都实

这样一套校验逻辑,这样就会有很多的代码和功能冗余,随着服务的扩大和业务需求的复杂度不断变化,修改校验逻辑变得相当麻烦,一处改,处处改。所以我们需要把认证授权服务单独出来,做成一个服务进行调用。

2. 授权服务的使用场景有哪些

授权服务并不是每个应用的接口直接去调用,判断哪些用户有权限访问接口。 而是通过API网关进行统一调用。用户所有的请求都必须先通过API网关,API网关在进行路由转发之前对该请求进行前置校验,我们可以方便的使用OAuth2认证授权服务来做单点登录等操作。

可以使用OAuth2来实现对多个服务的统一认证授权。

1.3 本项目主要操作流程

关于OAuth2的协议此处不再介绍,本项目演示主要的认证和授权步骤,简单来说就是客户端根据约定的ClientID、ClientSecret、Scope来从Access Token URL地址获取AccessToken,并经过AuthURL认证,用得到的AccessToken来访问其他资源接口。

项目结构如下,其中核心部分为core包下的config包中的三个配置类
从上往下分别是是授权服务配置,资源服务配置和安全配置

2. 代码示例

新建一个SpringBoot项目 auth-server,在项目中添加依赖build.gradle

  1. dependencies {
  2. compile('org.springframework.cloud:spring-cloud-starter-oauth2')
  3. compile('org.springframework.cloud:spring-cloud-starter-security')
  4. compile('org.springframework.boot:spring-boot-starter-data-mongodb')
  5. compile('org.springframework.boot:spring-boot-starter-data-redis')
  6. compile('org.springframework.cloud:spring-cloud-starter-eureka')
  7. testCompile('org.springframework.boot:spring-boot-starter-test')
  8. }

新建配置文件application.yml

  1. #服务器配置
  2. server:
  3. #端口
  4. port: 8020
  5. #服务器发现注册配置
  6. eureka:
  7. client:
  8. serviceUrl:
  9. #配置服务中心(可配置多个,用逗号隔开)
  10. defaultZone: https://www.apiboot.cn/eureka
  11. #spring配置
  12. spring:
  13. #应用配置
  14. application:
  15. #名称: OAuth2认证授权服务
  16. name: auth-server
  17. #数据库配置
  18. data:
  19. mongodb:
  20. port: 27017
  21. database: auth_server
  22. #安全配置
  23. security:
  24. #oauth2配置
  25. oauth2:
  26. resource:
  27. filter-order: 3

应用启动类添加注解

  1. /**
  2. * OAuth2认证授权服务
  3. * @ EnableDiscoveryClient 启用服务注册发现
  4. */
  5. @SpringBootApplication
  6. @EnableDiscoveryClient
  7. public class AuthServerApplication {
  8. public static void main(String[] args) {
  9. SpringApplication.run(AuthServerApplication.class, args);
  10. }
  11. }

新建账户实体类 Account.java

  1. /**
  2. * 账户实体类
  3. */
  4. public class Account {
  5. @Id
  6. private String id; // 主键
  7. private String userName; // 用户名
  8. private String passWord; // 密码
  9. private String[] roles; // 角色
  10. public String getId() {
  11. return id;
  12. }
  13. public void setId(String id) {
  14. this.id = id;
  15. }
  16. public String getUserName() {
  17. return userName;
  18. }
  19. public void setUserName(String userName) {
  20. this.userName = userName;
  21. }
  22. public String getPassWord() {
  23. return passWord;
  24. }
  25. public void setPassWord(String passWord) {
  26. this.passWord = passWord;
  27. }
  28. public String[] getRoles() {
  29. return roles;
  30. }
  31. public void setRoles(String[] roles) {
  32. this.roles = roles;
  33. }
  34. }

新建账户表数据操作类,MongoDB操作接口 AccountRepository.java

  1. /**
  2. * 账户数据库操作类
  3. * MongoDB操作接口
  4. */
  5. @Component
  6. public interface AccountRepository extends MongoRepository<Account, String> {
  7. /**
  8. * 根据用户名查找账户信息
  9. * @param username 用户名
  10. * @return 账户信息
  11. */
  12. Account findByUserName(String username);
  13. }

新建用户信息控制器 UserController.java

  1. /**
  2. * 用户信息控制器
  3. */
  4. @RestController
  5. public class UserController {
  6. @Autowired
  7. private AccountRepository accountRepository; // 账户数据操作
  8. /**
  9. * 初始化用户数据
  10. */
  11. @Autowired
  12. public void init(){
  13. // 为了方便测试,这里添加了两个不同角色的账户
  14. accountRepository.deleteAll();
  15. Account accountA = new Account();
  16. accountA.setUserName("admin");
  17. accountA.setPassWord("admin");
  18. accountA.setRoles(new String[]{"ROLE_ADMIN","ROLE_USER"});
  19. accountRepository.save(accountA);
  20. Account accountB = new Account();
  21. accountB.setUserName("guest");
  22. accountB.setPassWord("pass123");
  23. accountB.setRoles(new String[]{"ROLE_GUEST"});
  24. accountRepository.save(accountB);
  25. }
  26. /**
  27. * 获取授权用户的信息
  28. * @param user 当前用户
  29. * @return 授权信息
  30. */
  31. @GetMapping("/user")
  32. public Principal user(Principal user){
  33. return user;
  34. }
  35. }

新建用户信息服务类,实现 Spring Security的UserDetailsService接口方法,用于身份认证 DomainUserDetailsService.java

  1. /**
  2. * 用户信息服务
  3. * 实现 Spring Security的UserDetailsService接口方法,用于身份认证
  4. */
  5. @Service
  6. public class DomainUserDetailsService implements UserDetailsService {
  7. @Autowired
  8. private AccountRepository accountRepository; // 账户数据操作接口
  9. /**
  10. * 根据用户名查找账户信息并返回用户信息实体
  11. * @param username 用户名
  12. * @return 用于身份认证的 UserDetails 用户信息实体
  13. * @throws UsernameNotFoundException
  14. */
  15. @Override
  16. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  17. Account account = accountRepository.findByUserName(username);
  18. if (account!=null){
  19. return new User(account.getUserName(),account.getPassWord(), AuthorityUtils.createAuthorityList(account.getRoles()));
  20. }else {
  21. throw new UsernameNotFoundException("用户["+username+"]不存在");
  22. }
  23. }
  24. }

新建授权服务配置类AuthorizationServerConfig.java,继承AuthorizationServerConfigurerAdapter

  1. /**
  2. * 授权服务器配置
  3. * @ EnableAuthorizationServer 启用授权服务
  4. */
  5. @Configuration
  6. @EnableAuthorizationServer
  7. public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
  8. @Autowired
  9. private AuthenticationManager authenticationManager; // 认证管理器
  10. @Autowired
  11. private RedisConnectionFactory redisConnectionFactory; // redis连接工厂
  12. /**
  13. * 令牌存储
  14. * @return redis令牌存储对象
  15. */
  16. @Bean
  17. public TokenStore tokenStore() {
  18. return new RedisTokenStore(redisConnectionFactory);
  19. }
  20. @Override
  21. public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  22. endpoints.authenticationManager(this.authenticationManager);
  23. endpoints.tokenStore(tokenStore());
  24. }
  25. @Override
  26. public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
  27. security
  28. .tokenKeyAccess("permitAll()")
  29. .checkTokenAccess("isAuthenticated()");
  30. }
  31. @Override
  32. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  33. clients.inMemory()
  34. .withClient("android")
  35. .scopes("xx")
  36. .secret("android")
  37. .authorizedGrantTypes("password", "authorization_code", "refresh_token")
  38. .and()
  39. .withClient("webapp")
  40. .scopes("xx")
  41. .authorizedGrantTypes("implicit");
  42. }
  43. }

新建资源服务配置类,继承ResourceServerConfigurerAdapter
ResourceServerConfig.java

  1. /**
  2. * 资源服务配置
  3. * @ EnableResourceServer 启用资源服务
  4. */
  5. @Configuration
  6. @EnableResourceServer
  7. public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
  8. @Override
  9. public void configure(HttpSecurity http) throws Exception {
  10. http.requestMatcher(new OAuth2RequestedMatcher())
  11. .authorizeRequests()
  12. .antMatchers(HttpMethod.OPTIONS).permitAll()
  13. .anyRequest().authenticated();
  14. }
  15. /**
  16. * 定义OAuth2请求匹配器
  17. */
  18. private static class OAuth2RequestedMatcher implements RequestMatcher {
  19. @Override
  20. public boolean matches(HttpServletRequest request) {
  21. String auth = request.getHeader("Authorization");
  22. //判断来源请求是否包含oauth2授权信息,这里授权信息来源可能是头部的Authorization值以Bearer开头,或者是请求参数中包含access_token参数,满足其中一个则匹配成功
  23. boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");
  24. boolean haveAccessToken = request.getParameter("access_token")!=null;
  25. return haveOauth2Token || haveAccessToken;
  26. }
  27. }
  28. }

新建安全配置类,继承WebSecurityConfigurerAdapter
SecurityConfig.java

  1. /**
  2. * 安全配置
  3. * @ EnableWebSecurity 启用web安全配置
  4. * @ EnableGlobalMethodSecurity 启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
  5. */
  6. @Configuration
  7. @EnableWebSecurity
  8. @EnableGlobalMethodSecurity(prePostEnabled = true)
  9. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  10. /**
  11. * 注入用户信息服务
  12. * @return 用户信息服务对象
  13. */
  14. @Bean
  15. public UserDetailsService userDetailsService() {
  16. return new DomainUserDetailsService();
  17. }
  18. /**
  19. * 全局用户信息
  20. * @param auth 认证管理
  21. * @throws Exception 用户认证异常信息
  22. */
  23. @Autowired
  24. public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
  25. auth.userDetailsService(userDetailsService());
  26. }
  27. /**
  28. * 认证管理
  29. * @return 认证管理对象
  30. * @throws Exception 认证异常信息
  31. */
  32. @Override
  33. @Bean
  34. public AuthenticationManager authenticationManagerBean() throws Exception {
  35. return super.authenticationManagerBean();
  36. }
  37. /**
  38. * http安全配置
  39. * @param http http安全对象
  40. * @throws Exception http安全异常信息
  41. */
  42. @Override
  43. protected void configure(HttpSecurity http) throws Exception {
  44. http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated().and()
  45. .httpBasic().and().csrf().disable();
  46. }
  47. }

3. 演示

假设我们现在要直接访问 /user接口,我们打开PoatMan直接请求该接口

发现返回了401状态码,并且报了Unauthorized 未经授权的错误,提示信息显示 访问此资源需要完全身份验证。 添加了Spring Security+OAuth2后,所有的资源访问都需要通过token

3.1 获取access_token

启动服务,打开PostMan切换到Authorization页卡,Type类型选择Basic Auth,Username和Password填写授权服务配置中对应的withClient和secret的值,这里都写android

点击Update Request,切换到Headers页卡,发现请求头里多了个Authorization参数,参数值就是根据Authorization页卡填写的授权信息生成的,要获取token必须有该参数值

使用post方法访问授权服务的 /oauth/token地址,post参数需要填写grant_type、username、password。点击send请求,将会返回如下access_token信息。

拿到access_token后,就可以在请求其他资源接口的时候携带上该token参数值获取该角色可获取的资源,打开浏览器访问 /user接口并携带上 access_token参数值

3.2 使用正确的姿势获取access_token,并根据access_token获取资源

切换到Authorization页卡,选择OAuth2.0,点击Get New Access Token

TokenName可以随意填写,其他信息根据实际情况填写。点击Request Token后,将会跳出输入用户名和密码的页面(这个操作其实就是根据用户名和密码登录并获取AccessToken)

登录成功后,我们看到左侧有个我们刚刚新建的auth,点击auth,右侧会显示该请求获取到的AccessToken信息。点击UseToken

点击Use Token后,发现请求头Headers页卡里添加了Authorization的参数值

点击Send,请求/user接口,正常返回用户授权信息

到此 OAuth2认证授权服务已经搭建完毕了,关于api-gateway和OAuth2认证授权服务的整合调用,会在下一篇文章中写到,敬请期待。

 

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/505691
推荐阅读
相关标签
  

闽ICP备14008679号