spring boot 2.0 整合 oauth2 authorization code授權碼模式

oauth2 authorization code 大致流程

  1. 用戶打開客戶端后闲擦,客戶端要求用戶給予授權冯勉。
  2. 用戶同意給予客戶端授權萌壳。
  3. 客戶端使用授權得到的code亦镶,向認證服務器申請token令牌。
  4. 認證服務器對客戶端進行認證以后袱瓮,確認無誤缤骨,同意發(fā)放令牌。
  5. 客戶端請求資源時攜帶token令牌尺借,向資源服務器申請獲取資源绊起。
  6. 資源服務器確認令牌無誤,同意向客戶端開放資源燎斩。

security oauth2 整合的核心配置類

  1. 授權認證服務配置 AuthorizationServerConfiguration
  2. security 配置 SecurityConfiguration

工程結構目錄

image.png

pom.xml

<artifactId>security_oauth2_authorization</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>security_oauth2_authorization</name>
<description>oauth2 authorization_code 授權模式</description>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <security-jwt.version>1.0.9.RELEASE</security-jwt.version>
    <jjwt.version>0.9.0</jjwt.version>
  <spring-cloud.version>Finchley.RC2</spring-cloud.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-jwt</artifactId>
        <version>${security-jwt.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

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

授權認證服務配置類 AuthorizationServerConfiguration

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private MyUserDetailsService userDetailsService;


    @Override
    public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        String secret = passwordEncoder.encode("secret");
        clients.inMemory() // 使用in-memory存儲
                .withClient("client") // client_id
                .secret(secret) // client_secret
                //.autoApprove(true)  ∈帷//如果為true 則不會跳轉到授權頁面,而是直接同意授權返回code
                .authorizedGrantTypes("authorization_code","refresh_token") // 該client允許的授權類型
                .scopes("app"); // 允許的授權范圍
    }

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService)
                .accessTokenConverter(accessTokenConverter())
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); //支持GET  POST  請求獲取token;
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                String userName = authentication.getUserAuthentication().getName();
                final Map<String, Object> additionalInformation = new HashMap<String, Object>();
                additionalInformation.put("user_name", userName);
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
                OAuth2AccessToken token = super.enhance(accessToken, authentication);
                return token;
            }
        };
        //converter.setSigningKey("bcrypt");
        KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource("kevin_key.jks"), "123456".toCharArray())
                .getKeyPair("kevin_key");
        converter.setKeyPair(keyPair);
        return converter;
    }
}

web 安全配置 WebSecurityConfig

@Order(10)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailsService userDetailsFitService;
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/","/oauth/**","/login","/health", "/css/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsFitService).passwordEncoder(passwordEncoder());
        auth.parentAuthenticationManager(authenticationManagerBean());
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

}

url 注冊配置 MvcConfig

@Configuration
public class MvcConfig implements  WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login"); //自定義的登陸頁面
        registry.addViewController("/oauth/confirm_access").setViewName("oauth_approval"); //自定義的授權頁面
    }
}

# security 登陸認證 MyUserDetailsService
@Service
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
        if ("admin".equalsIgnoreCase(name)) {
            User user = mockUser();
            return user;
        }
        return null;
    }

    private User mockUser() {
        Collection<GrantedAuthority> authorities = new HashSet<>();
        authorities.add(new SimpleGrantedAuthority("admin"));
        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        String pwd = passwordEncoder.encode("123456");
        User user = new User("admin",pwd,authorities);
        return user;
    }
}

自定義登陸頁面 login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陸</title>
    <link  rel="stylesheet" crossorigin="anonymous">

</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <div class="portlet-body">
                <form class="form-horizontal" action="login" role="form" method="post">
                    <div class="form-group">
                        <label for="username" class="col-md-2 control-label">username</label>
                        <div class="col-md-6">
                            <input type="text" class="form-control" id="username" name="username" placeholder="username"> </div>
                    </div>
                    <div class="form-group">
                        <label for="password" class="col-md-2 control-label">Password</label>
                        <div class="col-md-6">
                            <input type="password" class="form-control" id="password" name="password" placeholder="password"> </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-offset-2 col-md-10">
                            <button type="submit" class="btn blue">登陸</button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>

</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"  crossorigin="anonymous"></script>
<script src="https://cdn.bootcss.com/bootstrap/4.1.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
</body>
</html>

自定義授權頁面 oauth_approval.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>授權</title>
    <link  rel="stylesheet" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <h2>你是否授權client_id=client訪問你的受保護資源?</h2>
    <div style="height: 20px;"></div>
    <form id="confirmationForm" name="confirmationForm"
          action="../oauth/authorize" method="post">

    <input name="user_oauth_approval" value="true" type="hidden"/>
    <button class="btn btn-primary" type="submit">Approve</button>
    </form>
    <div style="height: 20px;"></div>
    <form id="denyForm" name="confirmationForm"
          action="../oauth/authorize" method="post">
        <input name="user_oauth_approval" value="false" type="hidden"/>
        <button class="btn btn-primary" type="submit">Deny</button>
    </form>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"  crossorigin="anonymous"></script>
<script src="https://cdn.bootcss.com/bootstrap/4.1.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
</body>
</html>

application.yml

server:
  port: 18084

spring:
  application:
    name: oauth2-server   # 應用名稱

  thymeleaf:
        prefix: classpath:/templates/

logging:
  level:
    org.springframework.security: DEBUG

1. 訪問oauth2 服務

http://localhost:18084/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://baidu.com&state=123

  client_id:第三方應用在授權服務器注冊的 Id

  response_type:固定值 code栅表。

  redirect_uri:授權服務器授權重定向哪兒的 URL笋鄙。

  scope:權限

  state:隨機字符串,可以省略

如果未登陸則出現(xiàn)登錄頁面谨读,輸入用戶名:admin 密碼:123456 登陸系統(tǒng)


image.png

2. 成功登陸后自動跳轉到授權頁面

image.png
  1. 點擊“approve” 同意授權獲取code返回:https://www.baidu.com/?code=1LerDZ&state=123
    image.png
  2. 點擊“deny” 拒絕授權 返回:https://www.baidu.com/?error=access_denied&error_description=User%20denied%20access&state=123
    image.png

3. 攜帶授權之后返回的code 獲取token

http://localhost:18084/oauth/token?client_id=client&grant_type=authorization_code&redirect_uri=http://baidu.com&code=bzxoHn
如果沒有登陸會出現(xiàn)登陸頁面 輸入:賬號 client 密碼 secret 登陸

image.png

這里的賬號和密碼 是我們注冊的 client_id 和 client_secret
image.png

成功登陸后獲取token


image.png

4.攜帶toekn 訪問資源

http://localhost:18084/users/list?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiKiJdLCJ1c2VyX25hbWUiOiJxaWFvcnVsYWkiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNTI5MjMxMzE3LCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6Ijc1Mzg3OWMyLTI4ZDktNDgxMy04YTAxLWZkNzQ4OGNlOWRkMCIsImNsaWVudF9pZCI6ImNsaWVudF8yIn0.V9I2lBYKk7sNsygj_bwrJZF06A8LhZx2x_MHmapppGE

demo地址:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末局装,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子劳殖,更是在濱河造成了極大的恐慌铐尚,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哆姻,死亡現(xiàn)場離奇詭異宣增,居然都是意外死亡,警方通過查閱死者的電腦和手機矛缨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門爹脾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帖旨,“玉大人,你說我怎么就攤上這事灵妨〗庠模” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵泌霍,是天一觀的道長货抄。 經(jīng)常有香客問我,道長朱转,這世上最難降的妖魔是什么蟹地? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮藤为,結果婚禮上怪与,老公的妹妹穿的比我還像新娘。我一直安慰自己缅疟,他們只是感情好分别,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著存淫,像睡著了一般茎杂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纫雁,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音倾哺,去河邊找鬼轧邪。 笑死,一個胖子當著我的面吹牛羞海,可吹牛的內容都是我干的忌愚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼却邓,長吁一口氣:“原來是場噩夢啊……” “哼硕糊!你這毒婦竟也來了?” 一聲冷哼從身側響起腊徙,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤简十,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后撬腾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體螟蝙,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年民傻,在試婚紗的時候發(fā)現(xiàn)自己被綠了胰默。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片场斑。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖牵署,靈堂內的尸體忽然破棺而出漏隐,到底是詐尸還是另有隱情,我是刑警寧澤奴迅,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布青责,位于F島的核電站,受9級特大地震影響半沽,放射性物質發(fā)生泄漏爽柒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一者填、第九天 我趴在偏房一處隱蔽的房頂上張望浩村。 院中可真熱鬧,春花似錦占哟、人聲如沸心墅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怎燥。三九已至,卻和暖如春蜜暑,著一層夾襖步出監(jiān)牢的瞬間铐姚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工肛捍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留隐绵,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓拙毫,卻偏偏與公主長得像依许,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缀蹄,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內容