新聞中心
圖片來(lái)自 Pexels

十多年成都創(chuàng)新互聯(lián)公司網(wǎng)站建設(shè),由一走到現(xiàn)在,當(dāng)中離不開(kāi)團(tuán)隊(duì)頑強(qiáng)的創(chuàng)業(yè)精神,離不開(kāi)伴隨我們同行的客戶(hù)與專(zhuān)業(yè)的合作伙伴,創(chuàng)力信息一直秉承以“見(jiàn)一個(gè)客戶(hù),了解一個(gè)行業(yè),交一個(gè)朋友”的方式為經(jīng)營(yíng)理念,提出“讓每一個(gè)客戶(hù)成為我們的終身客戶(hù)”為目標(biāo),以為用戶(hù)提供精細(xì)化服務(wù),全面滿(mǎn)足用戶(hù)需求為宗旨,誠(chéng)信經(jīng)營(yíng),更大限度為用戶(hù)創(chuàng)造價(jià)值。期待邁向下一個(gè)更好的十多年。
網(wǎng)上關(guān)于實(shí)現(xiàn) SSO 的文章一大堆,但是當(dāng)你真的照著寫(xiě)的時(shí)候就會(huì)發(fā)現(xiàn)根本不是那么回事兒,簡(jiǎn)直讓人抓狂,尤其是對(duì)于我這樣的菜鳥(niǎo)。
幾經(jīng)曲折,終于搞定了,決定記錄下來(lái),以便后續(xù)查看。先來(lái)看一下效果:
準(zhǔn)備
①單點(diǎn)登錄
最常見(jiàn)的例子是,我們打開(kāi)淘寶 APP,首頁(yè)就會(huì)有天貓、聚劃算等服務(wù)的鏈接,當(dāng)你點(diǎn)擊以后就直接跳過(guò)去了,并沒(méi)有讓你再登錄一次。
下面這個(gè)圖是我在網(wǎng)上找的,我覺(jué)得畫(huà)得比較明白:
可惜有點(diǎn)兒不清晰,于是我又畫(huà)了個(gè)簡(jiǎn)版的:
重要的是理解:
- SSO 服務(wù)端和 SSO 客戶(hù)端直接是通過(guò)授權(quán)以后發(fā)放 Token 的形式來(lái)訪(fǎng)問(wèn)受保護(hù)的資源。
- 相對(duì)于瀏覽器來(lái)說(shuō),業(yè)務(wù)系統(tǒng)是服務(wù)端,相對(duì)于 SSO 服務(wù)端來(lái)說(shuō),業(yè)務(wù)系統(tǒng)是客戶(hù)端。
- 瀏覽器和業(yè)務(wù)系統(tǒng)之間通過(guò)會(huì)話(huà)正常訪(fǎng)問(wèn)。
- 不是每次瀏覽器請(qǐng)求都要去 SSO 服務(wù)端去驗(yàn)證,只要瀏覽器和它所訪(fǎng)問(wèn)的服務(wù)端的會(huì)話(huà)有效它就可以正常訪(fǎng)問(wèn)。
利用 OAuth2 實(shí)現(xiàn)單點(diǎn)登錄
接下來(lái),只講跟本例相關(guān)的一些配置,不講原理,不講為什么。
眾所周知,在 OAuth2 在有授權(quán)服務(wù)器、資源服務(wù)器、客戶(hù)端這樣幾個(gè)角色,當(dāng)我們用它來(lái)實(shí)現(xiàn) SSO 的時(shí)候是不需要資源服務(wù)器這個(gè)角色的,有授權(quán)服務(wù)器和客戶(hù)端就夠了。
授權(quán)服務(wù)器當(dāng)然是用來(lái)做認(rèn)證的,客戶(hù)端就是各個(gè)應(yīng)用系統(tǒng),我們只需要登錄成功后拿到用戶(hù)信息以及用戶(hù)所擁有的權(quán)限即可。
之前我一直認(rèn)為把那些需要權(quán)限控制的資源放到資源服務(wù)器里保護(hù)起來(lái)就可以實(shí)現(xiàn)權(quán)限控制,其實(shí)是我想錯(cuò)了,權(quán)限控制還得通過(guò) Spring Security 或者自定義攔截器來(lái)做。
①Spring Security 、OAuth2、JWT、SSO
在本例中,一定要分清楚這幾個(gè)的作用:
首先,SSO 是一種思想,或者說(shuō)是一種解決方案,是抽象的,我們要做的就是按照它的這種思想去實(shí)現(xiàn)它。
其次,OAuth2 是用來(lái)允許用戶(hù)授權(quán)第三方應(yīng)用訪(fǎng)問(wèn)他在另一個(gè)服務(wù)器上的資源的一種協(xié)議,它不是用來(lái)做單點(diǎn)登錄的,但我們可以利用它來(lái)實(shí)現(xiàn)單點(diǎn)登錄。
在本例實(shí)現(xiàn) SSO 的過(guò)程中,受保護(hù)的資源就是用戶(hù)的信息(包括,用戶(hù)的基本信息,以及用戶(hù)所具有的權(quán)限)。
而我們想要訪(fǎng)問(wèn)這這一資源就需要用戶(hù)登錄并授權(quán),OAuth2 服務(wù)端負(fù)責(zé)令牌的發(fā)放等操作,這令牌的生成我們采用 JWT,也就是說(shuō) JWT 是用來(lái)承載用戶(hù)的 Access_Token 的。
最后,Spring Security 是用于安全訪(fǎng)問(wèn)的,這里我們我們用來(lái)做訪(fǎng)問(wèn)權(quán)限控制。
認(rèn)證服務(wù)器配置
Maven 依賴(lài):
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0 org.springframework.boot spring-boot-starter-parent 2.1.3.RELEASE com.cjs.sso oauth2-sso-auth-server 0.0.1-SNAPSHOT oauth2-sso-auth-server 1.8 org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-security org.springframework.security.oauth.boot spring-security-oauth2-autoconfigure 2.1.3.RELEASE org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.springframework.session spring-session-data-redis mysql mysql-connector-java runtime org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.security spring-security-test test org.apache.commons commons-lang3 3.8.1 com.alibaba fastjson 1.2.56 org.springframework.boot spring-boot-maven-plugin
這里面最重要的依賴(lài)是:spring-security-oauth2-autoconfigure。
application.yml:
- spring:
- datasource:
- url: jdbc:mysql://localhost:3306/permission
- username: root
- password: 123456
- driver-class-name: com.mysql.jdbc.Driver
- jpa:
- show-sql: true
- session:
- store-type: redis
- redis:
- host: 127.0.0.1
- password: 123456
- port: 6379
- server:
- port: 8080
AuthorizationServerConfig(重要):
- package com.cjs.sso.config;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.Primary;
- import org.springframework.security.core.token.DefaultToken;
- 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.token.DefaultTokenServices;
- import org.springframework.security.oauth2.provider.token.TokenStore;
- import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
- import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
- import javax.sql.DataSource;
- /**
- * @author ChengJianSheng
- * @date 2019-02-11
- */
- @Configuration
- @EnableAuthorizationServer
- public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
- @Autowired
- private DataSource dataSource;
- @Override
- public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
- security.allowFormAuthenticationForClients();
- security.tokenKeyAccess("isAuthenticated()");
- }
- @Override
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
- clients.jdbc(dataSource);
- }
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- endpoints.accessTokenConverter(jwtAccessTokenConverter());
- endpoints.tokenStore(jwtTokenStore());
- // endpoints.tokenServices(defaultTokenServices());
- }
- /*@Primary
- @Bean
- public DefaultTokenServices defaultTokenServices() {
- DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
- defaultTokenServices.setTokenStore(jwtTokenStore());
- defaultTokenServices.setSupportRefreshToken(true);
- return defaultTokenServices;
- }*/
- @Bean
- public JwtTokenStore jwtTokenStore() {
- return new JwtTokenStore(jwtAccessTokenConverter());
- }
- @Bean
- public JwtAccessTokenConverter jwtAccessTokenConverter() {
- JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
- jwtAccessTokenConverter.setSigningKey("cjs"); // Sets the JWT signing key
- return jwtAccessTokenConverter;
- }
- }
說(shuō)明:
- 別忘了 @EnableAuthorizationServer。
- Token 存儲(chǔ)采用的是 JWT。
- 客戶(hù)端以及登錄用戶(hù)這些配置存儲(chǔ)在數(shù)據(jù)庫(kù),為了減少數(shù)據(jù)庫(kù)的查詢(xún)次數(shù),可以從數(shù)據(jù)庫(kù)讀出來(lái)以后再放到內(nèi)存中。
WebSecurityConfig(重要):
- package com.cjs.sso.config;
- import com.cjs.sso.service.MyUserDetailsService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.builders.WebSecurity;
- import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- /**
- * @author ChengJianSheng
- * @date 2019-02-11
- */
- @Configuration
- @EnableWebSecurity
- public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
- @Autowired
- private MyUserDetailsService userDetailsService;
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userDetailsService).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.formLogin()
- .loginPage("/login")
- .and()
- .authorizeRequests()
- .antMatchers("/login").permitAll()
- .anyRequest()
- .authenticated()
- .and().csrf().disable().cors();
- }
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
- }
自定義登錄頁(yè)面(一般來(lái)講都是要自定義的):
- package com.cjs.sso.controller;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.GetMapping;
- /**
- * @author ChengJianSheng
- * @date 2019-02-12
- */
- @Controller
- public class LoginController {
- @GetMapping("/login")
- public String login() {
- return "login";
- }
- @GetMapping("/")
- public String index() {
- return "index";
- }
- }
自定義登錄頁(yè)面的時(shí)候,只需要準(zhǔn)備一個(gè)登錄頁(yè)面,然后寫(xiě)個(gè) Controller 令其可以訪(fǎng)問(wèn)到即可,登錄頁(yè)面表單提交的時(shí)候 method 一定要是 post,最重要的時(shí)候 action 要跟訪(fǎng)問(wèn)登錄頁(yè)面的 url 一樣。
千萬(wàn)記住了,訪(fǎng)問(wèn)登錄頁(yè)面的時(shí)候是 GET 請(qǐng)求,表單提交的時(shí)候是 POST 請(qǐng)求,其他的就不用管了。
Ela Admin - HTML5 Admin Template
定義客戶(hù)端,如下圖:
加載用戶(hù),登錄賬戶(hù):
- package com.cjs.sso.domain;
- import lombok.Data;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.userdetails.User;
- import java.util.Collection;
- /**
- * 大部分時(shí)候直接用User即可不必?cái)U(kuò)展
- * @author ChengJianSheng
- * @date 2019-02-11
- */
- @Data
- public class MyUser extends User {
- private Integer departmentId; // 舉個(gè)例子,部門(mén)ID
- private String mobile; // 舉個(gè)例子,假設(shè)我們想增加一個(gè)字段,這里我們?cè)黾右粋€(gè)mobile表示手機(jī)號(hào)
- public MyUser(String username, String password, Collection extends GrantedAuthority> authorities) {
- super(username, password, authorities);
- }
- public MyUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection extends GrantedAuthority> authorities) {
- super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
- }
- }
加載登錄賬戶(hù):
- package com.cjs.sso.service;
- import com.alibaba.fastjson.JSON;
- import com.cjs.sso.domain.MyUser;
- import com.cjs.sso.entity.SysPermission;
- import com.cjs.sso.entity.SysUser;
- import lombok.extern.slf4j.Slf4j;
- 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.security.crypto.password.PasswordEncoder;
- import org.springframework.stereotype.Service;
- import org.springframework.util.CollectionUtils;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * @author ChengJianSheng
- * @date 2019-02-11
- */
- @Slf4j
- @Service
- public class MyUserDetailsService implements UserDetailsService {
- @Autowired
- private PasswordEncoder passwordEncoder;
- @Autowired
- private UserService userService;
- @Autowired
- private PermissionService permissionService;
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- SysUser sysUser = userService.getByUsername(username);
- if (null == sysUser) {
- log.warn("用戶(hù){}不存在", username);
- throw new UsernameNotFoundException(username);
- }
- List
permissionList = permissionService.findByUserId(sysUser.getId()); - List
authorityList = new ArrayList<>(); - if (!CollectionUtils.isEmpty(permissionList)) {
- for (SysPermission sysPermission : permissionList) {
- authorityList.add(new SimpleGrantedAuthority(sysPermission.getCode()));
- }
- }
- MyUser myUser = new MyUser(sysUser.getUsername(), passwordEncoder.encode(sysUser.getPassword()), authorityList);
- log.info("登錄成功!用戶(hù): {}", JSON.toJSONString(myUser));
- return myUser;
- }
- }
驗(yàn)證:
當(dāng)我們看到這個(gè)界面的時(shí)候,表示認(rèn)證服務(wù)器配置完成。
兩個(gè)客戶(hù)端
Maven 依賴(lài):
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0 org.springframework.boot spring-boot-starter-parent 2.1.3.RELEASE com.cjs.sso oauth2-sso-client-member 0.0.1-SNAPSHOT oauth2-sso-client-member Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-oauth2-client org.springframework.boot spring-boot-starter-security org.springframework.security.oauth.boot spring-security-oauth2-autoconfigure 2.1.3.RELEASE org.springframework.boot spring-boot-starter-thymeleaf org.thymeleaf.extras thymeleaf-extras-springsecurity5 3.0.4.RELEASE org.springframework.boot spring-boot-starter-web com.h2database h2 runtime org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.security spring-security-test test org.springframework.boot spring-boot-maven-plugin
application.yml:
- server:
- port: 8082
- servlet:
- context-path: /memberSystem
- security:
- oauth2:
- client:
- client-id: UserManagement
- client-secret: user123
- access-token-uri: http://localhost:8080/oauth/token
- user-authorization-uri: http://localhost:8080/oauth/authorize
- resource:
- jwt:
- key-uri: http://localhost:8080/oauth/token_key
這里 context-path 不要設(shè)成 /,不然重定向獲取 code 的時(shí)候回被攔截。
WebSecurityConfig:
- package com.cjs.example.config;
- import com.cjs.example.util.EnvironmentUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.builders.WebSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- /**
- * @author ChengJianSheng
- * @date 2019-03-03
- */
- @EnableOAuth2Sso
- @Configuration
- public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
- &n
當(dāng)前標(biāo)題:?jiǎn)吸c(diǎn)登錄(SSO),一看就會(huì),一做就錯(cuò)!
網(wǎng)頁(yè)地址:http://www.dlmjj.cn/article/djphchs.html


咨詢(xún)
建站咨詢(xún)
