点击上方 "程序员小乐" ,关注公众号 8点20分,第一时间与你相约 每日英文 Smile and stop complaining about the things you can't change. Time keeps ticking whether you're happy or sad. 保持微笑,停止抱怨那些改变不了的事。无论你开心与否,时间总是不等人的。 每日掏心话 即使生命柔弱,飘摇,像风雨中的一朵小花,也要努力的绽放,去触摸阳光的温暖。 来自:废物大师兄 | 责编:乐乐 链接:cnblogs.com/cjsblog/p/10548022.html 图片来自网络 往日回顾:详细介绍!Linux 上几种常用的文件传输方式 01 前言 之前更新过一篇单点登录(我去!原来单点登录这么简单,这下糗大了!),没看的可以去了解下! 技术这东西吧,看别人写的好像很简单似的,到自己去写的时候就各种问题,“一看就会,一做就错”。网上关于实现SSO的文章一大堆,但是当你真的照着写的时候就会发现根本不是那么回事儿,简直让人抓狂,尤其是对于我这样的菜鸟。几经曲折,终于搞定了,决定记录下来,以便后续查看。先来看一下效果 02 准备 2.1. 单点登录 最常见的例子是,我们打开淘宝APP,首页就会有天猫、聚划算等服务的链接,当你点击以后就直接跳过去了,并没有让你再登录一次 下面这个图是我在网上找的,我觉得画得比较明白: 可惜有点儿不清晰,于是我又画了个简版的: 重要的是理解: 03 利用OAuth2实现单点登录 接下来,只讲跟本例相关的一些配置,不讲原理,不讲为什么 众所周知,在OAuth2在有授权服务器、资源服务器、客户端这样几个角色,当我们用它来实现SSO的时候是不需要资源服务器这个角色的,有授权服务器和客户端就够了。 授权服务器当然是用来做认证的,客户端就是各个应用系统,我们只需要登录成功后拿到用户信息以及用户所拥有的权限即可 之前我一直认为把那些需要权限控制的资源放到资源服务器里保护起来就可以实现权限控制,其实是我想错了,权限控制还得通过Spring Security或者自定义拦截器来做 3.1. Spring Security 、OAuth2、JWT、SSO 在本例中,一定要分清楚这几个的作用 首先,SSO是一种思想,或者说是一种解决方案,是抽象的,我们要做的就是按照它的这种思想去实现它 其次,OAuth2是用来允许用户授权第三方应用访问他在另一个服务器上的资源的一种协议,它不是用来做单点登录的,但我们可以利用它来实现单点登录。在本例实现SSO的过程中,受保护的资源就是用户的信息(包括,用户的基本信息,以及用户所具有的权限),而我们想要访问这这一资源就需要用户登录并授权,OAuth2服务端负责令牌的发放等操作,这令牌的生成我们采用JWT,也就是说JWT是用来承载用户的Access_Token的 最后,Spring Security是用于安全访问的,这里我们我们用来做访问权限控制 04 认证服务器配置 4.1. Maven依赖 <?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.cjs.sso</groupId> <artifactId>oauth2-sso-auth-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>oauth2-sso-auth-server</name> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <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.1.3.RELEASE</version> </dependency> <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.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </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> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.56</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>"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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.cjs.sso</groupId> <artifactId>oauth2-sso-auth-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>oauth2-sso-auth-server</name> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <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.1.3.RELEASE</version> </dependency> <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.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </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> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.56</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project> 这里面最重要的依赖是:spring-security-oauth2-autoconfigure 4.2. 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: 6379server: port: 8080 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: 6379server: port: 8080 4.3. 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@EnableAuthorizationServerpublic 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; }}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@EnableAuthorizationServerpublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowiredprivate DataSource dataSource; @Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients(); security.tokenKeyAccess("isAuthenticated()"); } @Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource); } @Overridepublic 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; }*/ @Beanpublic JwtTokenStore jwtTokenStore() {return new JwtTokenStore(jwtAccessTokenConverter()); } @Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); jwtAccessTokenConverter.setSigningKey("cjs"); // Sets the JWT signing keyreturn jwtAccessTokenConverter; }} 说明: 别忘了@EnableAuthorizationServerToken存储采用的是JWT客户端以及登录用户这些配置存储在数据库,为了减少数据库的查询次数,可以从数据库读出来以后再放到内存中 4.4. 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@EnableWebSecuritypublic 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(); }}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@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowiredprivate MyUserDetailsService userDetailsService; @Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Overridepublic void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**"); } @Overrideprotected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login") .and() .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest() .authenticated() .and().csrf().disable().cors(); } @Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(); }} 4.5. 自定义登录页面(一般来讲都是要自定义的) package com.cjs.sso.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;/** * @author ChengJianSheng * @date 2019-02-12 */@Controllerpublic class LoginController { @GetMapping("/login") public String login() { return "login"; } @GetMapping("/") public String index() { return "index"; }}import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;/** * @author ChengJianSheng * @date 2019-02-12 */@Controllerpublic class LoginController { @GetMapping("/login")public String login() {return "login"; } @GetMapping("/")public String index() {return "index"; }} 自定义登录页面的时候,只需要准备一个登录页面,然后写个Controller令其可以访问到即可,登录页面表单提交的时候method一定要是post,最重要的时候action要跟访问登录页面的url一样 千万记住了,访问登录页面的时候是GET请求,表单提交的时候是POST请求,其它的就不用管了 <!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Ela Admin - HTML5 Admin Template</title> <meta name="description" content="Ela Admin - HTML5 Admin Template"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link type="text/css" rel="stylesheet" th:href="@{/assets/css/normalize.css}"> <link type="text/css" rel="stylesheet" th:href="@{/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css}"> <link type="text/css" rel="stylesheet" th:href="@{/assets/css/font-awesome.min.css}"> <link type="text/css" rel="stylesheet" th:href="@{/assets/css/style.css}"></head><body class="bg-dark"><div class="sufee-login d-flex align-content-center flex-wrap"> <div class="container"> <div class="login-content"> <div class="login-logo"> <h1 style="color: #57bf95;">欢迎来到王者荣耀</h1> </div> <div class="login-form"> <form th:action="@{/login}" method="post"> <div class="form-group"> <label>Username</label> <input type="text" class="form-control" name="username" placeholder="Username"> </div> <div class="form-group"> <label>Password</label> <input type="password" class="form-control" name="password" placeholder="Password"> </div> <div class="checkbox"> <label> <input type="checkbox"> Remember Me </label> <label class="pull-right"> <a href="#">Forgotten Password?</a> </label> </div> <button type="submit" class="btn btn-success btn-flat m-b-30 m-t-30" style="font-size: 18px;">登录</button> </form> </div> </div> </div></div><script type="text/javascript" th:src="@{/assets/js/jquery-2.1.4.min.js}"></script><script type="text/javascript" th:src="@{/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js}"></script><script type="text/javascript" th:src="@{/assets/js/main.js}"></script></body></html>"http://www.thymeleaf.org"><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Ela Admin - HTML5 Admin Template</title> <meta name="description" content="Ela Admin - HTML5 Admin Template"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link type="text/css" rel="stylesheet" th:href="@{/assets/css/normalize.css}"> <link type="text/css" rel="stylesheet" th:href="@{/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css}"> <link type="text/css" rel="stylesheet" th:href="@{/assets/css/font-awesome.min.css}"> <link type="text/css" rel="stylesheet" th:href="@{/assets/css/style.css}"></head><body class="bg-dark"><div class="sufee-login d-flex align-content-center flex-wrap"> <div class="container"> <div class="login-content"> <div class="login-logo"> <h1 style="color: #57bf95;">欢迎来到王者荣耀</h1> </div> <div class="login-form"> <form th:action="@{/login}" method="post"> <div class="form-group"> <label>Username</label> <input type="text" class="form-control" name="username" placeholder="Username"> </div> <div class="form-group"> <label>Password</label> <input type="password" class="form-control" name="password" placeholder="Password"> </div> <div class="checkbox"> <label> <input type="checkbox"> Remember Me </label> <label class="pull-right"> <a href="#">Forgotten Password?</a> </label> </div> <button type="submit" class="btn btn-success btn-flat m-b-30 m-t-30" style="font-size: 18px;">登录</button> </form> </div> </div> </div></div><script type="text/javascript" th:src="@{/assets/js/jquery-2.1.4.min.js}"></script><script type="text/javascript" th:src="@{/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js}"></script><script type="text/javascript" th:src="@{/assets/js/main.js}"></script></body></html> 4.6. 定义客户端 4.7. 加载用户 登录账户 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 */@Datapublic 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); }}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 */@Datapublic class MyUser extends User {private Integer departmentId; // 举个例子,部门IDprivate 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@Servicepublic 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<SysPermission> permissionList = permissionService.findByUserId(sysUser.getId()); List<SimpleGrantedAuthority> 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; }}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@Servicepublic class MyUserDetailsService implements UserDetailsService { @Autowiredprivate PasswordEncoder passwordEncoder; @Autowiredprivate UserService userService; @Autowiredprivate PermissionService permissionService; @Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser = userService.getByUsername(username);if (null == sysUser) {log.warn("用户{}不存在", username);throw new UsernameNotFoundException(username); } List<SysPermission> permissionList = permissionService.findByUserId(sysUser.getId()); List<SimpleGrantedAuthority> 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; }} 4.8. 验证 当我们看到这个界面的时候,表示认证服务器配置完成 05 两个客户端 5.1. Maven依赖 <?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.cjs.sso</groupId> <artifactId>oauth2-sso-client-member</artifactId> <version>0.0.1-SNAPSHOT</version> <name>oauth2-sso-client-member</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <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.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </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> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>"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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.cjs.sso</groupId> <artifactId>oauth2-sso-client-member</artifactId> <version>0.0.1-SNAPSHOT</version> <name>oauth2-sso-client-member</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <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.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </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> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project> 5.2. application.yml server: port: 8082 servlet: context-path: /memberSystemsecurity: 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_key8082 servlet: context-path: /memberSystemsecurity: 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的时候回被拦截 5.3. 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@Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private EnvironmentUtils environmentUtils; @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/bootstrap/**"); } @Override protected void configure(HttpSecurity http) throws Exception { if ("local".equals(environmentUtils.getActiveProfile())) { http.authorizeRequests().anyRequest().permitAll(); }else { http.logout().logoutSuccessUrl("http://localhost:8080/logout") .and() .authorizeRequests() .anyRequest().authenticated() .and() .csrf().disable(); } }}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@Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowiredprivate EnvironmentUtils environmentUtils; @Overridepublic void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/bootstrap/**"); } @Overrideprotected void configure(HttpSecurity http) throws Exception {if ("local".equals(environmentUtils.getActiveProfile())) { http.authorizeRequests().anyRequest().permitAll(); }else { http.logout().logoutSuccessUrl("http://localhost:8080/logout") .and() .authorizeRequests() .anyRequest().authenticated() .and() .csrf().disable(); } }} 说明: 最重要的注解是@EnableOAuth2Sso权限控制这里采用Spring Security方法级别的访问控制,结合Thymeleaf可以很容易做权限控制顺便多提一句,如果是前后端分离的话,前端需求加载用户的权限,然后判断应该显示那些按钮那些菜单 5.4. MemberController package com.cjs.example.controller;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.security.core.Authentication;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.security.Principal;/** * @author ChengJianSheng * @date 2019-03-03 */@Controller@RequestMapping("/member")public class MemberController { @GetMapping("/list") public String list() { return "member/list"; } @GetMapping("/info") @ResponseBody public Principal info(Principal principal) { return principal; } @GetMapping("/me") @ResponseBody public Authentication me(Authentication authentication) { return authentication; } @PreAuthorize("hasAuthority('member:save')") @ResponseBody @PostMapping("/add") public String add() { return "add"; } @PreAuthorize("hasAuthority('member:detail')") @ResponseBody @GetMapping("/detail") public String detail() { return "detail"; }}import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.security.core.Authentication;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.security.Principal;/** * @author ChengJianSheng * @date 2019-03-03 */@Controller@RequestMapping("/member")public class MemberController { @GetMapping("/list")public String list() {return "member/list"; } @GetMapping("/info") @ResponseBodypublic Principal info(Principal principal) {return principal; } @GetMapping("/me") @ResponseBodypublic Authentication me(Authentication authentication) {return authentication; } @PreAuthorize("hasAuthority('member:save')") @ResponseBody @PostMapping("/add")public String add() {return "add"; } @PreAuthorize("hasAuthority('member:detail')") @ResponseBody @GetMapping("/detail")public String detail() {return "detail"; }} 5.5. Order项目跟它是一样的 server: port: 8083 servlet: context-path: /orderSystemsecurity: oauth2: client: client-id: OrderManagement client-secret: order123 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_key8083 servlet: context-path: /orderSystemsecurity: oauth2: client: client-id: OrderManagement client-secret: order123 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 5.6. 关于退出 退出就是清空用于与SSO客户端建立的所有的会话,简单的来说就是使所有端点的Session失效,如果想做得更好的话可以令Token失效,但是由于我们用的JWT,故而撤销Token就不是那么容易,关于这一点,在官网上也有提到: 本例中采用的方式是在退出的时候先退出业务服务器,成功以后再回调认证服务器,但是这样有一个问题,就是需要主动依次调用各个业务服务器的logout 06 工程结构 附上源码: https://github.com/chengjiansheng/cjs-oauth2-sso-demo.git 07 演示 08 参考 09 文档 说说你对单点登录的理解? 欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。 欢迎各位读者加入程序员小乐技术群,在公众号后台回复“加群”或者“学习”即可。 猜你还想看 阿里、腾讯、百度、华为、京东最新面试题汇集 据说,这是史上最全的 Spring Boot 知识清单 切换到Linux工作,世界更美好 10年前被删的初恋,凌晨1点突然加我… 详细介绍!Linux 上几种常用的文件传输方式 不是每一个人都需要掌握一键登录!除非...... 关注「程序员小乐」,收看更多精彩内容