微服務架構開發一 oauth2認證服務器

建立此項目的目的:系統主要是對外提供數據,發佈數據,且對接系統較多,同時還要對發佈的接口做監控,審計等管理。前期項目考慮的比較簡單,使用技術有:springcloud,oauth2,spring security,zull,Eureka,redis等技術,單獨做一個認證服務器,資源權限驗證放在路由層,路由層使用zuul-ratelimit 進行api限流,zuul自己的斷路處理(FallbackProvider)。前期大致是這些功能,後期會慢慢增加。

首先創建認證服務器

 

創建認證中心類CustomAuthorizationServerConfig

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;

/**
 * @Description: 配置授權認證服務類
 * @author: wf
 * @Date: 2018-11-22
 * @Time: 13:41
 */
@Configuration
@EnableAuthorizationServer //授權認證中心
public class CustomAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 權限管理器
     */
    @Autowired
    private AuthenticationManager authenticationManager;
    /**
     * 數據源,保存token的時候需要
     */
    @Autowired
    private DataSource dataSource;
    /**
     * 設置保存token的方式,一共有五種,這裏採用數據庫的方式
     */
    @Autowired
    private TokenStore tokenStore;

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    /**
     * 自定義登錄或者鑑權失敗時的返回信息
     */
    @Resource(name = "webResponseExceptionTranslator")
    private WebResponseExceptionTranslator webResponseExceptionTranslator;

    @Resource(name = "signUserDetailService")
    private UserDetailsService userDetailsService;


    /******************配置區域**********************/


    /**
     * 用來配置授權(authorizatio)以及令牌(token)的訪問端點和令牌服務   核心配置  在啓動時就會進行配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //開啓密碼授權類型
        endpoints.authenticationManager(authenticationManager);
        //配置token存儲方式
        endpoints.tokenStore(tokenStore);
        //自定義登錄或者鑑權失敗時的返回信息
        endpoints.exceptionTranslator(webResponseExceptionTranslator);
    }

    /**
     * 用來配置令牌端點(Token Endpoint)的安全約束.
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        /**
         * 配置oauth2服務跨域
         */
        CorsConfigurationSource source = new CorsConfigurationSource() {
            @Override
            public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                CorsConfiguration corsConfiguration = new CorsConfiguration();
                corsConfiguration.addAllowedHeader("*");
                corsConfiguration.addAllowedOrigin(request.getHeader(HttpHeaders.ORIGIN));
                corsConfiguration.addAllowedMethod("*");
                corsConfiguration.setAllowCredentials(true);
                corsConfiguration.setMaxAge(3600L);
                return corsConfiguration;
            }
        };

        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients()
                .addTokenEndpointAuthenticationFilter(new CorsFilter(source));
    }

    /**
     * 用來配置客戶端詳情服務(ClientDetailsService),
     * 客戶端詳情信息在這裏進行初始化,  數據庫在進行client_id 與 client_secret驗證時   會使用這個service進行驗證
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }
}
 

資源提供端(主要是通過資源服務器中進行token驗證)

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

/**
 * @Description: 資源提供端的配置  目前和授權認證端處於同一應用
 * @author: wf
 * @Date: 2018-11-22
 * @Time: 16:58
 */
@Configuration
@EnableResourceServer //開啓資源提供服務的配置  是默認情況下spring security oauth2的http配置   會被WebSecurityConfigurerAdapter的配置覆蓋
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
          http
          .authorizeRequests()//定義哪些url需要被保護  哪些不需要保護
          .antMatchers("/oauth/token" , "oauth/check_token").permitAll()//定義這兩個鏈接不需要登錄可訪問
        /*  .antMatchers("/**").permitAll() *///定義所有的都不需要登錄  目前是測試需要
          .anyRequest().authenticated() //其他的都需要登錄
          //.antMatchers("/sys/**").hasRole("admin")///sys/**下的請求 需要有admin的角色
          .and()
          .formLogin().loginPage("/user/view/login")//如果未登錄則跳轉登錄的頁面   這兒可以控制登錄成功和登錄失敗跳轉的頁面
          .loginProcessingUrl("/user/login")
          .usernameParameter("username").passwordParameter("password").permitAll()//定義號碼與密碼的parameter
          .and()
          .csrf().disable();//防止跨站請求  spring security中默認開啓
    }


}
 

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import com.neusoft.auth.UserDetail;
import com.neusoft.domain.entity.Role;
import com.neusoft.domain.entity.User;
import com.neusoft.service.IUserService;
import com.neusoft.service.RoleService;

/**
 * @Description:
 * @author: wf
 * @Date: 2018-11-22
 * @Time: 15:07
 */
@Component("signUserDetailService")
public class SignUserDetaiServiceConfig implements UserDetailsService {

    @Autowired
    private IUserService userService;
    @Autowired
    private RoleService roleService;

    /**
     * 啓動刷新token授權類型,會判斷用戶是否還是存活的
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User currentUser = userService.getByName(s);
        if ( currentUser == null ) {
            throw new UsernameNotFoundException("用戶沒用找到");
        }
    
      List<Role>roles= roleService.queryByUser(currentUser.getId());
      List authorisList = new ArrayList();
      for(Role r : roles){
          authorisList.add(new SimpleGrantedAuthority(r.getAuth_role_id()));
      }
      UserDetails ud=   new UserDetail(currentUser,authorisList);
        return ud;
    }
}
 

 

import com.neusoft.auth.CustomerAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.annotation.Resource;

/**
 * @Description: //是默認情況下spring security的http配置 優於ResourceServerConfigurerAdapter的配置
 * @author: wf
 * @Date: 2018-11-22
 * @Time: 17:06
 */
@Configuration              //開啓三種可以在方法上面加權限控制的註解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityAdaptConfig extends WebSecurityConfigurerAdapter {

    /**
     * 獲取用戶的驗證配置類
     */
    @Resource(name = "signUserDetailService")
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Resource(name = "myCustomerAuthenticationProvider")
    private CustomerAuthenticationProvider customerAuthenticationProvider;
    
    

    /**
     * 配置資源服務端的http設置
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()//定義哪些url需要被保護  哪些不需要保護
                .antMatchers("/oauth/token" , "oauth/check_token","actuator/*").permitAll()//定義這兩個鏈接不需要登錄可訪問
              /*  .antMatchers("/**").permitAll() *///定義所有的都不需要登錄  目前是測試需要
                .anyRequest().authenticated() //其他的都需要登錄
                //.antMatchers("/sys/**").hasRole("admin")///sys/**下的請求 需要有admin的角色
                .and()
                .formLogin().loginPage("/login")//如果未登錄則跳轉登錄的頁面   這兒可以控制登錄成功和登錄失敗跳轉的頁面
                .usernameParameter("username").passwordParameter("password").permitAll()//定義號碼與密碼的parameter
                .and()
                .csrf().disable();//防止跨站請求  spring security中默認開啓


    }

    /**
     * 權限管理器  AuthorizationServerConfigurerAdapter認證中心需要的AuthenticationManager需要
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //目的是爲了前端獲取數據時獲取到整個form-data的數據,提供驗證器
        auth.authenticationProvider(customerAuthenticationProvider);
        //配置登錄user驗證處理器  以及密碼加密器  好讓認證中心進行驗證
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }

    /**
     * 需要配置這個支持password模式
     * support password grant type
     *
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return authenticationManager();
    }
}
 

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;

/**
 * @Description: AuthenticationManagerBuilder中的AuthenticationProvider是進行認證的核心
 * @author: wf
 * @Date: 2018-11-23
 * @Time: 9:11
 */
@Component("myCustomerAuthenticationProvider")
public class CustomerAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Resource(name = "signUserDetailService")
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 手動實現認證
     */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails , UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if ( authentication.getCredentials() == null ) {
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials" , "Bad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();
            if ( !this.passwordEncoder.matches(presentedPassword , userDetails.getPassword()) ) {
                this.logger.debug("Authentication failed: password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials" , "Bad credentials"));
            }
        }
    }

    /**
     * 手動加載user
     */
    @Override
    protected UserDetails retrieveUser(String s , UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
        return userDetailsService.loadUserByUsername(s);
    }
//    public static final Logger LOGGER = LoggerFactory.getLogger(CustomerAuthenticationProvider.class);
//

//
//
//    /**
//     *authentication是前臺拿過來的號碼密碼bean
//     */
//    @Override
//    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//        LOGGER.info("用戶輸入的用戶名是:" + authentication.getName());
//        LOGGER.info("用戶輸入的密碼是:" + authentication.getCredentials());
//
//
//        // 根據用戶輸入的用戶名獲取該用戶名已經在服務器上存在的用戶詳情,如果沒有則返回null
//        UserDetails userDetails = userDetailsService.loadUserByUsername(authentication.getName());
//        try {
//
//            LOGGER.info("服務器上已經保存的用戶名是:" + userDetails.getUsername());
//            LOGGER.info("服務器上保存的該用戶名對應的密碼是: " + userDetails.getPassword());
//            LOGGER.info("服務器上保存的該用戶對應的權限是:" + userDetails.getAuthorities());
//
//            if(authentication.getCredentials().equals(userDetails.getPassword())){
//                //驗證成功  將返回一個UsernamePasswordAuthenticaionToken對象
//                LOGGER.info("LOGIN SUCCESS !!!!!!!!!!!!!!!!!!!");
//                //分別返回用戶實體   輸入的密碼   以及用戶的權限
//                return new UsernamePasswordAuthenticationToken(userDetails,authentication.getCredentials(),userDetails.getAuthorities());
//            }
//        } catch (Exception e){
//            LOGGER.error("author failed, -------------------the error message is:-------- " + e);
//            throw e;
//        }
//        //如果驗證不同過則返回null或者拋出異常
//        return null;
//    }
//
//    /**
//     *
//     **/
//    @Override
//    public boolean supports(Class<?> aClass) {
//        return true;
//    }
}

 

import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.neusoft.domain.entity.User;

/**
 * @Description: 用戶信息的實體類
 * @author: wf
 * @Date: 2018-11-22
 * @Time: 15:46
 */
public class UserDetail implements UserDetails {
@Autowired
    private User user;

    private String id;
  private  Collection<? extends GrantedAuthority> roles;

    /**
     * 通過構造方法在UserDetailsService的方法中將查到的user注入進去
     */
    public UserDetail(User user, Collection<? extends GrantedAuthority> col) {
        this.user = user;
        if ( user != null ) {
            this.id = user.getId();
        }
        if(col!=null){
            this.roles=col;
        }
    }

    /**
     * 對當前的用戶賦予其應有的權限
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //添加權限
   

        return this.roles;
    }

    /**
     * 獲取密碼
     */
    @Override
    public String getPassword() {
        return user.getPassword();
    }

    /**
     * 獲取用戶名
     */
    @Override
    public String getUsername() {
        return user.getUsername();
    }

    /**
     * 賬戶是否未過期
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 賬戶是否未鎖定
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 證書是否未過期
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否有效   可對應數據庫中的delete_flag字段
     */
    @Override
    public boolean isEnabled() {
        return true;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}
pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.neusoft</groupId>
    <artifactId>auth_server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>auth_server</name>
    <description>Demo project for Spring Boot</description>

       <parent>
        <groupId>com.neusoft</groupId>
        <artifactId>center</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <pagehelper.version>1.1.0</pagehelper.version>
        <mybatis.generator.version>1.3.2</mybatis.generator.version>
        <fastjson.version>1.2.31</fastjson.version>
        <jackson.version>2.9.7</jackson.version>
    </properties>

    <dependencies>
    <dependency>
            <groupId>com.neusoft</groupId>
            <artifactId>center_common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--spring-security-oauth2 begin-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.0.6.RELEASE</version>
        </dependency>
           <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--spring-security-oauth2 end-->
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

application.yml配置

spring:
  profiles:
    #包含的配置項
    include:
    - single-datasource
    #      - quartz
    #啓用配置項級別
    active: prod
  #色彩日誌輸出
  output:
    ansi:
      enabled: always

  application:
    name: auth_server  
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8080/eureka/

mybatis:
  mapper-locations: classpath*:**/mappers/*.xml
  type-aliases-package: com.neusoft.domain.entity
  configuration:
    map-underscore-to-camel-case: true
    use-generated-keys: true
    use-column-label: true
    cache-enabled: true
    call-setters-on-nulls: true
logging:
  level:
    root: info
    org:
      springframework: info
server:
  port: 8081
以上是認證服務器的主要代碼,權限是用的動態編碼的,從數據庫中查找

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章