1 单点登录
单点登录,英文是 Single Sign On(缩写为 SSO)。即多个站点共用一台认证授权服务器,用户在站点登录后,可以免登录访问其他所有站点。而且,各站点间可以通过该登录状态直接交互。例如:
业务逻辑的使用
鉴权:
网关鉴权 微服务
没有 需要
鉴权 补充
CAS 是 Central Authentication Service 的缩写 —— 中央认证服务,一种独立开放指令协议,是 Yale 大学发起的一个企业级开源项目,旨在为 Web 应用系统提供一种可靠的 SSO 解决方案。
SSO是一种思想,而CAS只是实现这种思想的一种框架而已
CAS支持oauth2 协议
SSO是一种思想,或者说是一种解决方案,是抽象的,我们要做的就是按照它的这种思想去实现它
OAuth2是用来允许用户授权第三方应用访问他在另一个服务器上的资源的一种协议,它不是用来做单点登录的,但我们可以利用它来实现单点登录。
2 OAuth2
2.1 介绍
OAuth2是目前最流行的授权机制,用来授权第三方应用,获取用户数据。允许用户授权B应用不提供帐号密码的方式去访问该用户在A应用服务器上的某些特定资源。
2.2 oauth2.0四个角色
四个角色:
resource owner:资源所有者,这里可以理解为用户。
client:客户端,可以理解为一个第三方的应用程序 即微博 CSDN。
resource server:资源服务器,它存储用户或其它资源。
authorization server:
认证/授权服务器,它认证resource owner的身份,为 resource owner提供授权审批流程,并最终颁发授权令牌(Access Token)。
认证服务器只有一个 sysauth
资源服务器 订单 商品 支付
认证的流程
2.3 四种授权模式
* authorization_code 授权码模式
* password 密码模式
* client_credentials 客户端模式
* implicit 简单模式
* refresh_token 这个不是一种模式 是支持刷新令牌的意思
1 授权码模式
一般用于提供给第三方使用
2 简单模式
一般不会使用
3 密码模式
一般仅用于系统内部使用
4 客户端模式
一般不会使用
2.4 OAUTH2的spring cloud 微服务单点登录
用户:就是注册的用户
客户端:就是我们的前端项目
授权服务器:我们可以专门在后台创建一个专门管授权的微服务
资源微服务:像其他的订单微服务,什么搜索微服务拉都可以看做用户能够访问的资源
如果我们自己去用java实现OAUTH2.0标准,太麻烦了,幸好有spring-security-oauth2这个插件,就简单多了,spring-security-oauth2是基于spring-security框架完整实现oauth2协议的框架,具有oauth2中4种模式访问和第三方登录等功能。
2.5.4 四种模式测试
所有授权端点(EndterPoints),意思就是授权微服务启动后 可以访问哪些路径
认证服务器的ip以及端口号 localhost:8500
localhost:8500/oauth/token
路径 | 说明 |
/oauth/authoriz | 授权端点 |
/oauth/token | 令牌端点 获取token |
/oauth/confirm_access | 用户确认授权提交端点 |
/oauth/error | 授权服务错误信息端点 |
/oauth/check_token | 用于资源服务访问的令牌解析端点 |
/oauth/token_key | 提供公有密匙的端点,如果你使用JWT(RSA)令牌的 |
2.5 spring cloud alibaba 使用oauth2
2.5.1 创建父项目
2.5.2 启动nacos
2.5.3 创建授权微服务sys-auth
pom文件:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<!--security使用的jwt-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
授权码模式
1.配置核心的配置文件
授权的配置信息需要继承AuthorizationServerConfigurerAdapter
将授权的客户端的信息保存到内存里面
package com.sso.config;
import com.aaa.util.ConstUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
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 javax.annotation.Resource;
@Configuration
@EnableAuthorizationServer
public class MyOauthConfig extends AuthorizationServerConfigurerAdapter {
@Resource
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 将客户端的信息配置在内存中
clients.inMemory()
.withClient("admin")// 客户端id
.secret(passwordEncoder.encode("123456"))// 客户端密码
.redirectUris("http://localhost:8500")// 客户端重定向地址
.scopes("all")// 客户端授权范围
.authorities("all")// 客户端权限
.authorizedGrantTypes(ConstUtil.AUTHORIZATION_CODE,ConstUtil.REFRESH_TOKEN)// 客户端授权类型
.autoApprove(true)// 是否自动授权
.accessTokenValiditySeconds(3600)// token有效期
.refreshTokenValiditySeconds(3600*2);// 刷新token有效期
}
}
2.配置springsecurity的配置信息
package com.sso.config;
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.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;
@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll()//oauth接口全部允许访问
.anyRequest().authenticated()//其他接口需要认证
.and()
.formLogin().permitAll();//放行登录接口(表单)
http.csrf().disable();
}
@Bean
public PasswordEncoder getPassword(){
return new BCryptPasswordEncoder();
}
}
3.配置文件application.properties
server.port=8500
4.申请授权码:
http://localhost:8500/oauth/authorize?response_type=code&client_id=admin&scop=all
此时授权码是4WXzET
5. 根据授权码生成token
localhost:8500/oauth/token?grant_type=authorization_code&code=4WXzET&client_id=admin&redirect_url=http://www.baidu.com&scope=all
运行
简单模式:
修改授权的客户端的信息:
其他地方保持不变
访问地址:
localhost:8500/oauth/authorize?response_type=token&client_id=admin&scope=all
点击登录以后,会跳转到指定的redirect_uri,回调路径会,回调路径携带着令牌 access_token 、 expires_in 、 scope 等
客户端模式
配置文件的信息:
生成token
运行结果:
密码模式
application.properties文件
spring security主配置文件:
package com.sso.config;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.aaa.util.Result;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sso.service.MyUserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private MyUserService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
// http.formLogin()//.loginPage("/login.jsp")
// //.usernameParameter("uname") // 设置接收的自定义的用户的参数
// //.passwordParameter("pwd") // 设置接收自定义的用户的密码
.loginProcessingUrl("/userlogin") // 登录的路径 跟html或者jsp中的登录路径保持一致即可
// .successHandler(getSuccess())
// .permitAll() // 默认的成功的路径
// .and()
//
// .authorizeRequests().antMatchers("/","/login","/oauth/**").permitAll()
// //.and().authorizeRequests().antMatchers("/test/**").hasRole("user") // 有user的权限
// .anyRequest().authenticated() // 除去不需要认证的路径的其它路径都放行
// .and().cors()
// //.and().exceptionHandling().accessDeniedPage("/unauth.html") // 自定义没有权限的页面
// .and()
// .csrf().disable();// 关闭csrf的保护
// //
http.formLogin().loginPage("/login")
.successHandler(getSuccess()).permitAll()
.and()
.authorizeRequests().antMatchers("/","/oauth/**","/login").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();// 关闭csrf的保护; // 除去不需要认证的路径的其它路径都放行
//
// .and()
// .exceptionHandling().accessDeniedHandler(getAceessDefinedHandle())
// ;
}
/**
* sso
* @return
*/
@Bean
public AuthenticationSuccessHandler getSuccess() {
return (request, response, authentication) -> {
// 生成token
// oauth
// 1. 获取用户名 和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
// 认证服务器 2. 发出一个post请求 获取生成token
HttpRequest params = HttpUtil.createPost("localhost:8500/oauth/token");
// 设置请求参数
params.form("username",username);
params.form("password",password);
params.form("grant_type", "password");
params.form("client_id", "admin");
params.form("client_secret", "123456");
HttpResponse response1 = params.execute(); // 执行
// code body
String result = response1.body();
System.out.println("body:"+result);
JSONObject entries = JSONUtil.parseObj(body);
Object body = entries.get("access_token");
//
// token的值 存到redis
// 刷新token
// localhost:8500/oauth/token?grant_type=password&username=ww&password=123456&client_id=admin&client_secret=123456
Result result1= new Result(200,"成功",body);
ObjectMapper objectMapper = new ObjectMapper();
String jsonmessage = objectMapper.writeValueAsString(result1);
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.print(jsonmessage);
out.flush();
out.close();
};
}
//
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(getPasswordEncoder());
//
// auth.inMemoryAuthentication()
// .withUser("yyl")
// .password(getPasswordEncoder().encode("123456"))
// .roles("admin","user");
}
@Bean
public BCryptPasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager getAuthManager() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public AccessDeniedHandler getAceessDefinedHandle(){
return (request, response, accessDeniedException) -> {
Result result = Result.accessDefined(null);
printJsonData(result,response);
};
}
public void printJsonData(Result result, HttpServletResponse response) throws IOException {
// 以json的形式 传递除去
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(result);
// 响应到前端
response.setContentType("application/json;charset=utf8");
PrintWriter writer = response.getWriter();
writer.write(s);
writer.flush();
writer.close();
}
}
授权服务器配置文件
package com.sso.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import javax.annotation.Resource;
@Configuration
@EnableAuthorizationServer //
public class MyOauthConfig extends AuthorizationServerConfigurerAdapter {
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private AuthenticationManager authenticationManager;
/**
* 设置第三方的客户端的信息
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("admin") //第三方的名字
.secret(passwordEncoder.encode("123456")) //密码 加密
.scopes("all") // 客户端的授权范围
//.autoApprove(true)// 自动授权
/**
* 支持哪些认证模式
* authorization_code 授权码模式
* password 密码模式
* implicit 简单模式
* client_credentials 客户端模式
* refresh_token 这个不是一种模式 是支持刷新令牌的意思
*/
.authorizedGrantTypes("password","refresh_token") // 授权的类型
.accessTokenValiditySeconds(60*60)// token生效的时间是1分钟
.refreshTokenValiditySeconds(60*60*2);
//.redirectUris("http://www.baidu.com");// 跳转到某一个路径里面 路径的后面会跟一个?code= code就是我们的授权码
}
/**
* 配置凭证的信息
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(getTokenStore())
.accessTokenConverter(getConvert())
.authenticationManager(authenticationManager);
}
/**
* 进行安全的配置 生成token 校验token
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()") // /oauth/check_token
.tokenKeyAccess("permitAll"); // 生成token
}
/**
* 配置一个tokenStore
*/
@Bean
public TokenStore getTokenStore(){
return new JwtTokenStore(getConvert());
}
/**
* 构建 令牌
* @return
*/
@Bean
public JwtAccessTokenConverter getConvert(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//
jwtAccessTokenConverter.setSigningKey("test");
return jwtAccessTokenConverter;
}
}
测试:登录成功之后查看是否成功
资源服务器在使用的时候一定要将token的值添加到header中而且header中的key必须是Authorization对应的token的值也必须是bearer 类型的(以bear为开头后面一个空格)
2.5.5 搭建资源微服务
有了授权微服务,那么再搭建资源微服务,也就是以后我们写项目的时候主要写业务的微服务
pom文件:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
资源配置文件:
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class ResConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
//.access("@RbacConfig.hasPermission(request,authentication)")
.and()
.csrf().disable();
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey("dev");
return jwtAccessTokenConverter;
}
}
测试
是否可以使用
@RestController
public class MyController {
@PreAuthorize("hasRole('ROLE_admin1')")
@GetMapping("test")
public Result test(Authentication authentication){
System.out.println(authentication.getAuthorities());
// User principal = (User)authentication.getPrincipal();
// System.out.println(principal);
return new Result("test");
}
}
实战:
页面发出请求的时候使用的工具是axios
请求必须要经过网关
配置axios的基础路径 localhost:8808
登录
登录请求的时候
思路:
- 请求经过网关->sso->登录是否成功
成功->颁发令牌
不成功->返回认证失败
登录前端页面
发出请求的时候携带token
微服务:
错误: 启动类上要加@FeignClients注解
微服务之间进行调用
get请求
参数 传参的时候需要一个注解@RequestParam
Post请求
传参的时候 需要加一个注解 @RequestBody
踩坑记录:
微服务之间进行调用的时候
进行的是post请求 传递的参数是List 接收参数的时候使用实现类
调用的代码:
接收参数的时候一定要使用实现类
登录:
后端:
Oauth: 第三方客户端的信息 内存里面
用户的信息:
数据库里面
Springsecurity
MyUserDetailsService:
- 根据用户名查询用户的信息
员工的增删改查的服务 一般都是在系统管理模块 查询用户的信息的时候 需要和系统管理模块进行微服务之间的交互
Openfeign
Sys-system
- 根据用户的id 查询角色的id
根据用户的id 查询角色的id
一般都是在系统管理模块 查询用户的信息的时候 需要和系统管理模块进行微服务之间的交互
- 根据角色id查询角色的名字 (资源信息)
- 菜单的信息 X