因为公司的项目需要一个新开发的单点登录系统,以前从没有接触过登录安全方面的,然后最近这段时间充电学习使用Oauth2.0完成登录和授权,也是在学习完成之后项目搭建好之后写了这篇博客,用来记录学习的收获。
Oauth2.0协议,相信有需要使用的小伙伴们都已经了解过了,我就不赘述了,下面直接开始单点登录系统的搭建。
首先在Spring io上面下载一个Spring boot项目,基于Spring boot的注解能快速的实现我们想要的单点登录,在代码中我会插入一些自己对Spring Sucurity的理解。
<?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>
<groupId>com.oauthDemo</groupId>
<artifactId>oauth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>oauth</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.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>
<mysql.vsrsion>6.0.6</mysql.vsrsion>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--Druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.19</version>
</dependency>
<!--MySql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.vsrsion}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这是在项目中需要的依赖。
在这里我们首先讲一个Spring Security的安全验证流程,Spring Security 实现登录和授权实际上是使用的一个过滤器链
图片是百度Spring Security 过滤器链找到的,我加入了一点自己的理解,将过滤器链简单的归纳一下,让使用者对Spring Security的认证流程有一个简单的概念。
SecurityContextPersistenceFilter:
在浏览器发起一个请求的时候第一个经过的过滤器,这个过滤器实现的功能是,如果当前的请求中有经过认证的用户信息,就取出来,如果没有经过
用户的认证信息就把自身的存储的用户信息放进去,如果没有用户信息就直接通过。
FilterSecurityInterceptor:
从请求中获取认证信息,如果有认证的信息就会放行,如果请求中没有认证过的用户信息,就会抛一个异常,而它左边的ExceptionTranslationFilter 就是捕获整个过滤器链的异常,然后进行不同的操作,如果在FilterSecurityInterceptor 捕获的异常是用户未认证的异常,他就会根据Security的配置引导用户去到登录页面去进行登录操作。
UsernamePasswordAuthenticationFilter :
处理来自表单登录的请求,表单必须提供用户名和密码,在配置中,用户还能自己重写登录成功或者失败的Handler方法,来自定义登录成功和失败的处理,在这个过滤器中,会生成一个UsernamePasswordAuthenticationToken这个对象是一个未经过认证的token对象。这个对象中有两个构造方法,一个构造方法是生成未经过认证的token对象,一个是认证完成之后的生成经过认证的token对象。
BasicAuthenticationFilter:
这个过滤器和上一个表单的过滤器是一起的,只不过这个是过滤Basic登录请求的。
RememberMeAuthenticationFiter:
看前面就能理解这个过滤器的作用,记住我,当登录的表单有记住我这个勾选框,并且这个勾选框的nane要和Spring Security所指定的name一致,在用户登录成功后,这个过滤器就会在浏览器保存一个Cookie,并且在数据表中存储一个token信息和对应的用户名信息,下次打开的时候会先进入这个过滤器,如果浏览器有token的登录信息,并且和数据库中存储的信息对应就会默认是当前这个用户登录,会自动走一遍认证的流程,如果用户正常,不是异常状态,就可以免登陆,实现RememberMe记住我。
上面介绍的几个是Spring Security默认提供的过滤器链,上图中RememberMeAuthenticationFiter下面的过滤器是其他的登录方式的过滤器,但是实际上和表单登录的逻辑是一样,只不过在这样的过滤器中实现的是我们自己定义的登录逻辑。
经过上面对Security的认证流程简单认知后,我们开始来构建一个简单的授权登录的Oauthor2.0认证服务器。
首先我们在项目目录下新建一个Class,用来当做Security的配置类,虽然Security有默认的配置类,但是如果我们要实现我们自己的逻辑,就必须要自己来配置一些信息。
@Configuration public class webSecurityConfig extends WebSecurityConfigurerAdapter { // 查询用户的实现类 @Autowired private UserDetailsService UserServiceImpl;
@Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(UserServiceImpl).passwordEncoder(new BCryptPasswordEncoder()); } /** * 功能描述: 如果其他地方想要注入AuthenticationManager的话,这里必须要声明这个Bean */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /** * 这是Security的安全认证策略,默认的是所有请求都可以在授权之后访问 */ @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin() .and() .authorizeRequests() .anyRequest() .authenticated(); } }
这是一个非常简单的访问配置,意思是 使用表单登录,所有的请求,都需要经过认证之后才能访问。
实现的UserDetailsService是Security提供给我们的接口,让我们来自定义用户的登录逻辑。
/**
* @author by stephen
* @date 2018/10/18.
*/
@Service
public class UserServiceImpl implements UserDetailsService {
/**
*
* 功能描述: 实现Security中的从数据库中查询对象,这里可以引入操作数据库对象的方式,然后来从数据库中根据用户名
* 获取你自己的用户对象,然后返回一个Security的UserDetails对象
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User("user","123456",new ArrayList<>());
}
}
到这里,简单的Seucirty配置就配置完成了。
接下来就是Oauth2.0的配置,oauth2.0的配置和Sercurity配置类似,但是需要自己在项目中指定Mysql依赖和配置数据源,这里我就不将数据源的信息贴出来了,使用Oauth2.0在数据库中保存信息的话需要自己新建三张表,这三张表可以百度一下然后自己了解一下。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
@Configuration @EnableAuthorizationServer public class authorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { /** * 数据源信息 */ @Autowired private DataSource dataSource; @Autowired private TokenStore tokenStore; /** * 声明TokenStore实现 */ @Bean public TokenStore tokenStore() { return new JdbcTokenStore(dataSource); } /** * 声明 ClientDetails实现,这里有封装方法进过数据源验证请求提交的数据 */ @Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } /** * oauth2认证的客户端信息,连接的客户端必须要在oauth2中注册过 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource); } @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService UserServiceImpl; @Autowired private ClientDetailsService clientDetails; @Bean @Primary public DefaultTokenServices tokenServices() { DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setSupportRefreshToken(true); tokenServices.setTokenStore(tokenStore); return tokenServices; } /** * 该方法是用来配置Authorization Server endpoints的一些非安全特性的,比如token存储、token自定义、授权类型等等的 * 默认情况下,你不需要做任何事情,除非你需要密码授权,那么在这种情况下你需要提供一个AuthenticationManager */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); endpoints.tokenStore(tokenStore()); endpoints.userDetailsService(UserServiceImpl); endpoints.setClientDetailsService(clientDetails); endpoints.tokenServices(tokenServices()); //配置TokenServices参数 DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(endpoints.getTokenStore()); tokenServices.setSupportRefreshToken(true); tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toHours(1)); } }
最后还要加上资源服务器的配置,这次我们将资源服务器和认证服务器部署在一个项目内,正常来说是需要分开配置的。
@Configuration @EnableResourceServer public class resourceServerConfiguration extends ResourceServerConfigurerAdapter { /** * 资源服务器与授权服务器为一体,此处配置守保护的资源 * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception { http.requestMatchers().antMatchers("/user/**") .and() .authorizeRequests() .anyRequest().authenticated(); } }
到这里配置就差不多了,我们可以启动项目测试一下。
首先,我们在Spring boot 的启动类上做一点改造
@SpringBootApplication @RestController public class OauthApplication { @RequestMapping("/Hello") public String HelloOauth(){ return "成功!"; } public static void main(String[] args) { SpringApplication.run(OauthApplication.class, args); } }
然后启动项目,我们访问这个Hello这个地址
Spring Security会自动为我们跳转到登录界面,我们在这个登录界面来输入我们在UserDetailsService中的创建的User对象的用户名和密码点击登录。
他会自动帮我们跳会我们之前请求的地址,但是实际上我们的这些功能,Spring Security就可以实现了,接下来,我们来搭建两个项目,在这两个项目之间实现单点登录的效果。
在Spring io上面再次导出两个新的项目,加入如下的依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
客户端的配置其实相当渐变,我们只需要在Application上面加上单点登录的注解,然后让这个启动类可以被访问,来测试一下我们的单点登录
这是客户端A的
@SpringBootApplication @EnableOAuth2Sso @RestController public class OauthApplication { @RequestMapping("/findA") public String find(){ return "Aaaaaaaaaa"; } public static void main(String[] args) { SpringApplication.run(OauthApplication.class, args); } }
这是客户端B的
@SpringBootApplication @EnableOAuth2Sso @RestController public class OauthApplication { @RequestMapping("/findB") public String find(){ return "BBBBBB"; } public static void main(String[] args) { SpringApplication.run(OauthApplication.class, args); } }
然后我们启动之后请求就可以来测试一下是否可以单点登录了
访问A和B都会跳转到认证服务器的登录页面,我们登录其中一个页面:
然后我们返回第一个选项卡的位置,这是请求A的地址,我们重新请求一下A
请求都通过了认证。
到此我们简单的单点登录就完成了。