图片来自 Pexels

十多年成都创新互联公司网站建设,由一走到现在,当中离不开团队顽强的创业精神,离不开伴随我们同行的客户与专业的合作伙伴,创力信息一直秉承以“见一个客户,了解一个行业,交一个朋友”的方式为经营理念,提出“让每一个客户成为我们的终身客户”为目标,以为用户提供精细化服务,全面满足用户需求为宗旨,诚信经营,更大限度为用户创造价值。期待迈向下一个更好的十多年。
网上关于实现 SSO 的文章一大堆,但是当你真的照着写的时候就会发现根本不是那么回事儿,简直让人抓狂,尤其是对于我这样的菜鸟。
几经曲折,终于搞定了,决定记录下来,以便后续查看。先来看一下效果:
准备
①单点登录
最常见的例子是,我们打开淘宝 APP,首页就会有天猫、聚划算等服务的链接,当你点击以后就直接跳过去了,并没有让你再登录一次。
下面这个图是我在网上找的,我觉得画得比较明白:
可惜有点儿不清晰,于是我又画了个简版的:
重要的是理解:
- SSO 服务端和 SSO 客户端直接是通过授权以后发放 Token 的形式来访问受保护的资源。
- 相对于浏览器来说,业务系统是服务端,相对于 SSO 服务端来说,业务系统是客户端。
- 浏览器和业务系统之间通过会话正常访问。
- 不是每次浏览器请求都要去 SSO 服务端去验证,只要浏览器和它所访问的服务端的会话有效它就可以正常访问。
利用 OAuth2 实现单点登录
接下来,只讲跟本例相关的一些配置,不讲原理,不讲为什么。
众所周知,在 OAuth2 在有授权服务器、资源服务器、客户端这样几个角色,当我们用它来实现 SSO 的时候是不需要资源服务器这个角色的,有授权服务器和客户端就够了。
授权服务器当然是用来做认证的,客户端就是各个应用系统,我们只需要登录成功后拿到用户信息以及用户所拥有的权限即可。
之前我一直认为把那些需要权限控制的资源放到资源服务器里保护起来就可以实现权限控制,其实是我想错了,权限控制还得通过 Spring Security 或者自定义拦截器来做。
①Spring Security 、OAuth2、JWT、SSO
在本例中,一定要分清楚这几个的作用:
首先,SSO 是一种思想,或者说是一种解决方案,是抽象的,我们要做的就是按照它的这种思想去实现它。
其次,OAuth2 是用来允许用户授权第三方应用访问他在另一个服务器上的资源的一种协议,它不是用来做单点登录的,但我们可以利用它来实现单点登录。
在本例实现 SSO 的过程中,受保护的资源就是用户的信息(包括,用户的基本信息,以及用户所具有的权限)。
而我们想要访问这这一资源就需要用户登录并授权,OAuth2 服务端负责令牌的发放等操作,这令牌的生成我们采用 JWT,也就是说 JWT 是用来承载用户的 Access_Token 的。
最后,Spring Security 是用于安全访问的,这里我们我们用来做访问权限控制。
认证服务器配置
Maven 依赖:
- 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
这里面最重要的依赖是: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;
- }
- }
说明:
- 别忘了 @EnableAuthorizationServer。
- Token 存储采用的是 JWT。
- 客户端以及登录用户这些配置存储在数据库,为了减少数据库的查询次数,可以从数据库读出来以后再放到内存中。
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();
- }
- }
自定义登录页面(一般来讲都是要自定义的):
- 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";
- }
- }
自定义登录页面的时候,只需要准备一个登录页面,然后写个 Controller 令其可以访问到即可,登录页面表单提交的时候 method 一定要是 post,最重要的时候 action 要跟访问登录页面的 url 一样。
千万记住了,访问登录页面的时候是 GET 请求,表单提交的时候是 POST 请求,其他的就不用管了。
Ela Admin - HTML5 Admin Template
定义客户端,如下图:
加载用户,登录账户:
- 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;
- /**
- * 大部分时候直接用User即可不必扩展
- * @author ChengJianSheng
- * @date 2019-02-11
- */
- @Data
- public class MyUser extends User {
- private Integer departmentId; // 举个例子,部门ID
- private String mobile; // 举个例子,假设我们想增加一个字段,这里我们增加一个mobile表示手机号
- 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);
- }
- }
加载登录账户:
- 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("用户{}不存在", 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("登录成功!用户: {}", JSON.toJSONString(myUser));
- return myUser;
- }
- }
验证:
当我们看到这个界面的时候,表示认证服务器配置完成。
两个客户端
Maven 依赖:
- 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 不要设成 /,不然重定向获取 code 的时候回被拦截。
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
分享名称:单点登录(SSO),一看就会,一做就错!
链接URL:http://www.jxjierui.cn/article/djphchs.html


咨询
建站咨询
