赞
踩
为了保证服务对外的安全性,往往都会在服务接口采用权限校验机制,为了防止客户端在发起请求中途被篡改数据等安全方面的考虑,还会有一些签名校验的机制。
在分布式微服务架构的系统中,我们把原本复杂的系统业务拆分成了若干个独立的微服务应用,我们不得不在每个微服务中都实
这样一套校验逻辑,这样就会有很多的代码和功能冗余,随着服务的扩大和业务需求的复杂度不断变化,修改校验逻辑变得相当麻烦,一处改,处处改。所以我们需要把认证授权服务单独出来,做成一个服务进行调用。
授权服务并不是每个应用的接口直接去调用,判断哪些用户有权限访问接口。 而是通过API网关进行统一调用。用户所有的请求都必须先通过API网关,API网关在进行路由转发之前对该请求进行前置校验,我们可以方便的使用OAuth2认证授权服务来做单点登录等操作。
可以使用OAuth2来实现对多个服务的统一认证授权。
关于OAuth2的协议此处不再介绍,本项目演示主要的认证和授权步骤,简单来说就是客户端根据约定的ClientID、ClientSecret、Scope来从Access Token URL地址获取AccessToken,并经过AuthURL认证,用得到的AccessToken来访问其他资源接口。
项目结构如下,其中核心部分为core包下的config包中的三个配置类
从上往下分别是是授权服务配置,资源服务配置和安全配置
新建一个SpringBoot项目 auth-server,在项目中添加依赖build.gradle
- dependencies {
- compile('org.springframework.cloud:spring-cloud-starter-oauth2')
- compile('org.springframework.cloud:spring-cloud-starter-security')
- compile('org.springframework.boot:spring-boot-starter-data-mongodb')
- compile('org.springframework.boot:spring-boot-starter-data-redis')
- compile('org.springframework.cloud:spring-cloud-starter-eureka')
- testCompile('org.springframework.boot:spring-boot-starter-test')
- }
新建配置文件application.yml
- #服务器配置
- server:
- #端口
- port: 8020
-
- #服务器发现注册配置
- eureka:
- client:
- serviceUrl:
- #配置服务中心(可配置多个,用逗号隔开)
- defaultZone: https://www.apiboot.cn/eureka
-
- #spring配置
- spring:
- #应用配置
- application:
- #名称: OAuth2认证授权服务
- name: auth-server
- #数据库配置
- data:
- mongodb:
- port: 27017
- database: auth_server
-
- #安全配置
- security:
- #oauth2配置
- oauth2:
- resource:
- filter-order: 3
应用启动类添加注解
- /**
- * OAuth2认证授权服务
- * @ EnableDiscoveryClient 启用服务注册发现
- */
- @SpringBootApplication
- @EnableDiscoveryClient
- public class AuthServerApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(AuthServerApplication.class, args);
- }
-
- }
新建账户实体类 Account.java
- /**
- * 账户实体类
- */
- public class Account {
- @Id
- private String id; // 主键
- private String userName; // 用户名
- private String passWord; // 密码
- private String[] roles; // 角色
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public String getUserName() {
- return userName;
- }
-
- public void setUserName(String userName) {
- this.userName = userName;
- }
-
- public String getPassWord() {
- return passWord;
- }
-
- public void setPassWord(String passWord) {
- this.passWord = passWord;
- }
-
- public String[] getRoles() {
- return roles;
- }
-
- public void setRoles(String[] roles) {
- this.roles = roles;
- }
- }
新建账户表数据操作类,MongoDB操作接口 AccountRepository.java
- /**
- * 账户数据库操作类
- * MongoDB操作接口
- */
- @Component
- public interface AccountRepository extends MongoRepository<Account, String> {
-
- /**
- * 根据用户名查找账户信息
- * @param username 用户名
- * @return 账户信息
- */
- Account findByUserName(String username);
- }
新建用户信息控制器 UserController.java
- /**
- * 用户信息控制器
- */
- @RestController
- public class UserController {
-
- @Autowired
- private AccountRepository accountRepository; // 账户数据操作
-
- /**
- * 初始化用户数据
- */
- @Autowired
- public void init(){
-
- // 为了方便测试,这里添加了两个不同角色的账户
- accountRepository.deleteAll();
-
- Account accountA = new Account();
- accountA.setUserName("admin");
- accountA.setPassWord("admin");
- accountA.setRoles(new String[]{"ROLE_ADMIN","ROLE_USER"});
- accountRepository.save(accountA);
-
- Account accountB = new Account();
- accountB.setUserName("guest");
- accountB.setPassWord("pass123");
- accountB.setRoles(new String[]{"ROLE_GUEST"});
- accountRepository.save(accountB);
- }
-
- /**
- * 获取授权用户的信息
- * @param user 当前用户
- * @return 授权信息
- */
- @GetMapping("/user")
- public Principal user(Principal user){
- return user;
- }
- }
新建用户信息服务类,实现 Spring Security的UserDetailsService接口方法,用于身份认证 DomainUserDetailsService.java
- /**
- * 用户信息服务
- * 实现 Spring Security的UserDetailsService接口方法,用于身份认证
- */
- @Service
- public class DomainUserDetailsService implements UserDetailsService {
-
- @Autowired
- private AccountRepository accountRepository; // 账户数据操作接口
-
- /**
- * 根据用户名查找账户信息并返回用户信息实体
- * @param username 用户名
- * @return 用于身份认证的 UserDetails 用户信息实体
- * @throws UsernameNotFoundException
- */
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- Account account = accountRepository.findByUserName(username);
- if (account!=null){
- return new User(account.getUserName(),account.getPassWord(), AuthorityUtils.createAuthorityList(account.getRoles()));
- }else {
- throw new UsernameNotFoundException("用户["+username+"]不存在");
- }
- }
- }
新建授权服务配置类AuthorizationServerConfig.java,继承AuthorizationServerConfigurerAdapter
- /**
- * 授权服务器配置
- * @ EnableAuthorizationServer 启用授权服务
- */
- @Configuration
- @EnableAuthorizationServer
- public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
-
- @Autowired
- private AuthenticationManager authenticationManager; // 认证管理器
-
- @Autowired
- private RedisConnectionFactory redisConnectionFactory; // redis连接工厂
-
- /**
- * 令牌存储
- * @return redis令牌存储对象
- */
- @Bean
- public TokenStore tokenStore() {
- return new RedisTokenStore(redisConnectionFactory);
- }
-
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- endpoints.authenticationManager(this.authenticationManager);
- endpoints.tokenStore(tokenStore());
- }
-
- @Override
- public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
- security
- .tokenKeyAccess("permitAll()")
- .checkTokenAccess("isAuthenticated()");
- }
-
- @Override
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
- clients.inMemory()
- .withClient("android")
- .scopes("xx")
- .secret("android")
- .authorizedGrantTypes("password", "authorization_code", "refresh_token")
- .and()
- .withClient("webapp")
- .scopes("xx")
- .authorizedGrantTypes("implicit");
- }
-
- }
新建资源服务配置类,继承ResourceServerConfigurerAdapter
ResourceServerConfig.java
- /**
- * 资源服务配置
- * @ EnableResourceServer 启用资源服务
- */
- @Configuration
- @EnableResourceServer
- public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
-
- @Override
- public void configure(HttpSecurity http) throws Exception {
- http.requestMatcher(new OAuth2RequestedMatcher())
- .authorizeRequests()
- .antMatchers(HttpMethod.OPTIONS).permitAll()
- .anyRequest().authenticated();
- }
-
- /**
- * 定义OAuth2请求匹配器
- */
- private static class OAuth2RequestedMatcher implements RequestMatcher {
- @Override
- public boolean matches(HttpServletRequest request) {
- String auth = request.getHeader("Authorization");
- //判断来源请求是否包含oauth2授权信息,这里授权信息来源可能是头部的Authorization值以Bearer开头,或者是请求参数中包含access_token参数,满足其中一个则匹配成功
- boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");
- boolean haveAccessToken = request.getParameter("access_token")!=null;
- return haveOauth2Token || haveAccessToken;
- }
- }
-
- }
新建安全配置类,继承WebSecurityConfigurerAdapter
SecurityConfig.java
- /**
- * 安全配置
- * @ EnableWebSecurity 启用web安全配置
- * @ EnableGlobalMethodSecurity 启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
- */
- @Configuration
- @EnableWebSecurity
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
-
- /**
- * 注入用户信息服务
- * @return 用户信息服务对象
- */
- @Bean
- public UserDetailsService userDetailsService() {
- return new DomainUserDetailsService();
- }
-
- /**
- * 全局用户信息
- * @param auth 认证管理
- * @throws Exception 用户认证异常信息
- */
- @Autowired
- public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userDetailsService());
- }
-
- /**
- * 认证管理
- * @return 认证管理对象
- * @throws Exception 认证异常信息
- */
- @Override
- @Bean
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- /**
- * http安全配置
- * @param http http安全对象
- * @throws Exception http安全异常信息
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated().and()
- .httpBasic().and().csrf().disable();
- }
-
- }
假设我们现在要直接访问 /user接口,我们打开PoatMan直接请求该接口
发现返回了401状态码,并且报了Unauthorized 未经授权的错误,提示信息显示 访问此资源需要完全身份验证。 添加了Spring Security+OAuth2后,所有的资源访问都需要通过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参数值
切换到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认证授权服务的整合调用,会在下一篇文章中写到,敬请期待。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。