spring boot oauth2單點(diǎn)登錄(一):前后端分離例子

相關(guān)文章

1、spring boot oauth2單點(diǎn)登錄(一)-前后端分離例子
2掌呜、spring boot oauth2單點(diǎn)登錄(二)-客戶端信息存儲(chǔ)
3措伐、spring boot oauth2單點(diǎn)登錄(三)-token存儲(chǔ)方式
4线召、spring boot oauth2單點(diǎn)登錄(四)-code存儲(chǔ)方式

源碼地址

后端:https://gitee.com/fengchangxin/sso
前端:https://gitee.com/fengchangxin/sso-page

準(zhǔn)備

后端:三個(gè)spring boot應(yīng)用驱入,auth(授權(quán)管理),client1(客戶端應(yīng)用1)吊骤,client2(客戶端應(yīng)用2)缎岗。
前端:三個(gè)Vue項(xiàng)目,auth白粉,client1密强,client2茅郎。分別對應(yīng)三個(gè)后端應(yīng)用。
工具:nginx
域名:oauth.com或渤,client1.com系冗,client2.com,分別對應(yīng)三個(gè)系統(tǒng)薪鹦。
開發(fā)環(huán)境:先在host文件添加上面三個(gè)域名掌敬。
端口:
后端服務(wù)auth(8080),client1(8081)池磁,client2(8082)奔害。
前端auth(8083),client1(8084)地熄,client2(8085)华临。
依賴:

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

測試地址:
client1:http://client1.com/client1Page/#/home
client2:http://client2.com/client2Page/#/home
登錄用戶:admin/123456

備注:此篇文章對應(yīng)的授權(quán)中心:auth
此篇文章是開發(fā)環(huán)境下的端考,nginx配置生產(chǎn)環(huán)境有所不同雅潭,請注意。文章中列出的只是一些關(guān)鍵地方却特,一些細(xì)節(jié)還是要看整個(gè)項(xiàng)目代碼扶供。

配置nginx

都是監(jiān)聽80端口和三個(gè)域名,例如client1裂明,/client1/轉(zhuǎn)到后端服務(wù)椿浓,/clientPage/轉(zhuǎn)到Vue前端服務(wù),同時(shí)要配置~ .*.(js|css)$闽晦,一些js和css文件轉(zhuǎn)到前端服務(wù)地址扳碍,不然無法訪問到本地Vue服務(wù)。至于為什么要用nginx仙蛉,那是要把前端和后端的地址都是同域名下笋敞,解決跨域問題。

    server {
    listen       80;
    server_name  client1.com;
    location /client1/ {
        proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://localhost:8081/client1/;
    }

    location /client1Page/ {
        proxy_pass http://localhost:8084/;
    }
    location ~ .*\.(js|css)$ {
        proxy_pass http://localhost:8084;
    }
    }

    server {
    listen       80;
    server_name  client2.com;
    location /client2/ {
        proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_pass http://localhost:8082/client2/;
    }
    location /client2Page/ {
        proxy_pass http://localhost:8085/;
    }

    location ~ .*\.(js|css)$ {
        proxy_pass http://localhost:8085;
    }
    }

    server {
        listen       80;
        server_name  oauth.com;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;
    location /auth/ {
        proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://localhost:8080/auth/;
    }
    location /authPage/ {
        proxy_pass http://localhost:8083/;
    }

    location ~ .*\.(js|css)$ {
        proxy_pass http://localhost:8083;
    }
    }

一捅儒、授權(quán)管理系統(tǒng)

1液样、后端授權(quán)管理服務(wù)

1.1自定義登錄成功振亮、登錄失敗巧还、未登錄的返回處理

未登錄處理
在這里做了兩個(gè)邏輯處理,根據(jù)參數(shù)isRedirect是否是true坊秸,如果是true則重定向到授權(quán)中心auth的前端登錄頁麸祷,若為空或false,則返回授權(quán)中心的后端授權(quán)接口褒搔,并帶上isRedirect=true阶牍,定義Result對象的code為800則為未登錄喷面。

@Component("unauthorizedEntryPoint")
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        Map<String, String[]> paramMap = request.getParameterMap();
        StringBuilder param = new StringBuilder();
        paramMap.forEach((k, v) -> {
            param.append("&").append(k).append("=").append(v[0]);
        });
        param.deleteCharAt(0);
        String isRedirectValue = request.getParameter("isRedirect");
        if (!StringUtils.isEmpty(isRedirectValue) && Boolean.valueOf(isRedirectValue)) {
            response.sendRedirect("http://oauth.com/authPage/#/login?"+param.toString());
            return;
        }
        String authUrl = "http://oauth.com/auth/oauth/authorize?"+param.toString()+"&isRedirect=true";
        Result result = new Result();
        result.setCode(800);
        result.setData(authUrl);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter writer = response.getWriter();
        ObjectMapper mapper = new ObjectMapper();
        writer.print(mapper.writeValueAsString(result));
        writer.flush();
        writer.close();
    }
}

登錄成功返回處理

@Component("successAuthentication")
public class SuccessAuthentication extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter writer = response.getWriter();
        Result result = new Result();
        result.setCode(0);
        result.setMsg("成功");
        ObjectMapper mapper = new ObjectMapper();
        writer.println(mapper.writeValueAsString(result));
        writer.flush();
        writer.close();
    }
}

登錄失敗返回處理

@Component("failureAuthentication")
public class FailureAuthentication extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter writer = response.getWriter();
        Result result = new Result();
        result.setCode(1000);
        result.setMsg("登錄失敗");
        ObjectMapper mapper = new ObjectMapper();
        writer.println(mapper.writeValueAsString(result));
        writer.flush();
        writer.close();
    }
}

1.2登錄配置

在內(nèi)存添加兩個(gè)登錄用戶,正式使用是存儲(chǔ)在數(shù)據(jù)庫走孽,后續(xù)文章再寫惧辈。同時(shí)把1.1的實(shí)現(xiàn)添加到此處。

@EnableWebSecurity
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private SuccessAuthentication successAuthentication;
    @Autowired
    private FailureAuthentication failureAuthentication;
    @Autowired
    private UnauthorizedEntryPoint unauthorizedEntryPoint;

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

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint)
                .and()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().successHandler(successAuthentication).failureHandler(failureAuthentication);
    }

    @Bean
    @Override
    public UserDetailsService userDetailsServiceBean() {
        Collection<UserDetails> users = buildUsers();

        return new InMemoryUserDetailsManager(users);
    }

    private Collection<UserDetails> buildUsers() {
        String password = passwordEncoder().encode("123456");

        List<UserDetails> users = new ArrayList<>();

        UserDetails user_admin = User.withUsername("admin").password(password).authorities("ADMIN", "USER").build();
        UserDetails user_user1 = User.withUsername("user1").password(password).authorities("USER").build();

        users.add(user_admin);
        users.add(user_user1);

        return users;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

1.3客戶端應(yīng)用信息配置

在內(nèi)存添加client1和client2客戶端信息磕瓷,正式使用是存儲(chǔ)在數(shù)據(jù)庫盒齿,后續(xù)文章再寫。注意checkTokenAccess()困食,tokenKeyAccess()要配置边翁,不然無法啟動(dòng)客戶端應(yīng)用,報(bào)401硕盹,403之類的錯(cuò)誤符匾。

@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .checkTokenAccess("isAuthenticated()")
                .tokenKeyAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(inMemoryClientDetailsService());
    }


    @Bean
    public ClientDetailsService inMemoryClientDetailsService() throws Exception {
        return new InMemoryClientDetailsServiceBuilder()
                .withClient("client1")
                .secret(passwordEncoder.encode("client1_secret"))
                .scopes("all")
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .redirectUris("http://client1.com/client1/login")
                .accessTokenValiditySeconds(7200)
                .autoApprove(true)

                .and()
                .withClient("client2")
                .secret(passwordEncoder.encode("client2_secret"))
                .scopes("all")
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .redirectUris("http://client2.com/client2/login")
                .accessTokenValiditySeconds(7200)
                .autoApprove(true)

                .and()
                .build();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        super.configure(endpoints);
    }
}

1.4添加跨域

這里允許了所有的域訪問,可以根據(jù)客戶端應(yīng)用的域名來做限制瘩例,通過數(shù)據(jù)庫啊胶、緩存等方式來存儲(chǔ)允許訪問的域。

@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
public class CORSFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        //允許所有的域訪問仰剿,可以設(shè)置只允許自己的域訪問
        response.setHeader("Access-Control-Allow-Origin", "*");
        //允許所有方式的請求
        response.setHeader("Access-Control-Allow-Methods", "*");
        //頭信息緩存有效時(shí)長(如果不設(shè) Chromium 同時(shí)規(guī)定了一個(gè)默認(rèn)值 5 秒)创淡,沒有緩存將已OPTIONS進(jìn)行預(yù)請求
        response.setHeader("Access-Control-Max-Age", "3600");
        //允許的頭信息
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res);
        }
    }
}

二、后端客戶端服務(wù)

2.1登錄配置

配置所有接口都需要登錄才能訪問南吮,同時(shí)用@EnableOAuth2Sso注解標(biāo)注為客戶端琳彩。

@EnableOAuth2Sso
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.logout()
                .and()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
    }
}

2.2 yml配置文件

server:
  port: 8081
  servlet:
    context-path: /client1

security:
  oauth2:
    client:
      client-id: client1
      preEstablishedRedirectUri:
      client-secret: client1_secret
      access-token-uri: http://oauth.com/auth/oauth/token
      user-authorization-uri: http://oauth.com/auth/oauth/authorize
    resource:
      user-info-uri: http://oauth.com/auth/user
      token-info-uri: http://oauth.com/auth/oauth/check_token

2.3 回調(diào)接口

客戶端要實(shí)現(xiàn)一個(gè)根路徑回調(diào)接口,一般正常流程的回調(diào)地址是開始請求的接口地址部凑,但為什么是請求到根路徑我也還沒找到原因露乏,所以這里就實(shí)現(xiàn)根路徑接口,重定向到前端地址涂邀。

    @GetMapping("/")
    public void callback(HttpServletResponse response) throws IOException {
        response.sendRedirect("http://client1.com/client1Page/#/home");
    }

三瘟仿、前端應(yīng)用

具體請看碼云代碼。

3.1 客戶端前端

主要是一個(gè)按鈕比勉,發(fā)起請求后端服務(wù)劳较。當(dāng)未登錄時(shí)請求會(huì)返回800的錯(cuò)誤,則跳轉(zhuǎn)到授權(quán)中心的授權(quán)接口浩聋,注意要用window.location.href 開新標(biāo)簽頁跳轉(zhuǎn)观蜗,跳轉(zhuǎn)地址為UnauthorizedEntryPoint返回的地址。

3.2 授權(quán)管理前端

主要是一個(gè)登錄頁衣洁,當(dāng)客戶端未登錄時(shí)跳轉(zhuǎn)到授權(quán)接口墓捻,授權(quán)中心判斷未登錄則跳轉(zhuǎn)到此登錄頁。注意登錄成功之后頁面重新跳轉(zhuǎn)時(shí)也要用window.location.href 重新跳轉(zhuǎn)到授權(quán)接口坊夫。

四砖第、測試

先啟動(dòng)nginx撤卢,然后在開發(fā)工具分別啟動(dòng)所有項(xiàng)目,比如idea梧兼、webstore放吩,在瀏覽器中訪問http://client1.com/client1Page/#/homehttp://client2.com/client2Page/#/home
注意在登錄成功之后返回頁面并沒有顯示變化,并不是出問題羽杰,再次點(diǎn)擊按鈕即可查詢到數(shù)據(jù)屎慢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市忽洛,隨后出現(xiàn)的幾起案子腻惠,更是在濱河造成了極大的恐慌,老刑警劉巖欲虚,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件集灌,死亡現(xiàn)場離奇詭異,居然都是意外死亡复哆,警方通過查閱死者的電腦和手機(jī)欣喧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梯找,“玉大人唆阿,你說我怎么就攤上這事⌒獯福” “怎么了驯鳖?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長久免。 經(jīng)常有香客問我浅辙,道長,這世上最難降的妖魔是什么阎姥? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任记舆,我火速辦了婚禮,結(jié)果婚禮上呼巴,老公的妹妹穿的比我還像新娘泽腮。我一直安慰自己,他們只是感情好衣赶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布诊赊。 她就那樣靜靜地躺著,像睡著了一般屑埋。 火紅的嫁衣襯著肌膚如雪豪筝。 梳的紋絲不亂的頭發(fā)上痰滋,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天摘能,我揣著相機(jī)與錄音续崖,去河邊找鬼。 笑死团搞,一個(gè)胖子當(dāng)著我的面吹牛严望,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逻恐,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼像吻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了复隆?” 一聲冷哼從身側(cè)響起拨匆,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挽拂,沒想到半個(gè)月后惭每,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亏栈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年台腥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绒北。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡黎侈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闷游,到底是詐尸還是另有隱情峻汉,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布脐往,位于F島的核電站俱济,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钙勃。R本人自食惡果不足惜蛛碌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辖源。 院中可真熱鬧蔚携,春花似錦、人聲如沸克饶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矾湃。三九已至亡脑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背霉咨。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工蛙紫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人途戒。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓坑傅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喷斋。 傳聞我的和親對象是個(gè)殘疾皇子唁毒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353