用户认证Spring Security Oauth2 JWT

用户认证

用户认证与授权

  • 用户身份认证
    • 用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问。
    • 用户身份认证表现形式有:用户名密码登录,指纹打卡等方式。
  • 用户授权
    • 用户认证通过后去访问系统的资源,系统会判断用户是否拥有访问资源的权限,只允许访问有权限的系统资源,没有权限的资源将无法访问。

单点登录(Single Sign On,SSO)

  • 为了提高用户体验性需要实现用户只认证一次便可以在多个拥有访问权限的系统中访问,这个功能叫做单点登录。
  • SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

第三方认证(跨平台认证)

  • 当需要访问第三方系统的资源时需要首先通过第三方系统的认证(例如:微信认证),由第三方系统对用户认证通过,并授权资源的访问权限。

用户认证技术方案

单点登录

  • 分布式系统要实现单点登录,通常将认证系统独立抽取出来,并且将用户身份信息存储在单独的存储介质。通常存储在redis中。

  • 示例
    在这里插入图片描述

  • 单点登录功能实现
    在这里插入图片描述

    • 功能实现
      • 1、登录页面提交用户名密码。
      • 2、登录成功后生成token。Token相当于原来的jsessionid,字符串,可以使用uuid。
      • 3、把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json。
      • 4、使用String类型保存Session信息。可以使用“前缀:token”为key
      • 5、设置key的过期时间。模拟Session的过期时间。一般半个小时。
      • 6、把token写入cookie中。
      • 7、Cookie需要跨域。可以使用工具类。
      • 8、Cookie的有效期。关闭浏览器失效。
      • 9、登录成功。
  • 单点登录的特点

    • 认证系统为独立的系统
    • 各子系统通过Http或其它协议与认证系统通信,完成用户认证。
    • 用户身份信息存储在Redis集群
  • java中用户认证的框架实现单点登录

    • Apache Shiro
    • CAS
    • Spring security CAS

Oauth2认证

  • 第三方认证技术方案最主要是解决认证协议的通用标准问题,因为要实现跨系统认证,各系统之间要遵循一定的接口协议。
  • OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时,任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。
  • Oauth2认证流程示例
    在这里插入图片描述
    • (1)客户端请求第三方授权
    • (2)资源拥有者同意给客户端授权
    • (3)客户端获取到授权码,请求认证服务器申请令牌
    • (4)认证服务器向客户端响应令牌
    • (5)客户端请求资源服务器的资源
    • (6)资源服务器返回受保护的资源
  • Oauth2.0认证流程
    在这里插入图片描述
  • Oauth2参与实体:
    • 客户端Client:本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源。
    • 资源拥有者Resource Owner:通常为用户,也可以是应用程序,即该资源的拥有者。
    • 授权服务器(认证服务器)Authorization Server:用来对资源拥有的身份进行认证、对访问资源进行授权。
    • 资源服务器Resource Server:存储资源的服务器。

Spring security Oauth2认证解决方案

  • 采用Spring security + Oauth2完成用户认证及用户授权。
  • Spring security 是一个强大的和高度可定制的身份验证和访问控制框架,Spring security 框架集成了Oauth2协议。
  • 项目认证架构图
    在这里插入图片描述
    • 用户请求认证服务完成认证;
    • 认证服务下发用户身份令牌,拥有身份令牌表示身份合法;
    • 用户携带令牌请求资源服务,请求资源服务必先经过网关;
    • 网关校验用户身份令牌的合法性,合法放行继续访问,不合法表示用户未登录;
    • 资源服务获取令牌,根据令牌完成授权;
    • 资源服务完成授权则响应资源信息。

Spring Security Oauth2

搭建认证服务器

  • 搭建ucenter-auth工程
    在这里插入图片描述
  • 创建用户数据库
    在这里插入图片描述
    • 以“oauth_”开头的表都是spring Security 自带的表。
    • spring Security 主要使用oauth_client_details表。
      在这里插入图片描述

Oauth2授权码模式

  • 授权模式
    • 授权码模式(Authorization Code)
    • 隐式授权模式(Implicit)
    • 密码模式(Resource Owner PasswordCredentials)
      客户端模式(Client Credentials)
  • 授权码授权流程
    • 1、客户端请求第三方授权
    • 2、用户(资源拥有者)同意给客户端授权
    • 3、客户端获取到授权码,请求认证服务器申请令牌
    • 4、认证服务器向客户端响应令牌
    • 5、客户端请求资源服务器的资源,资源服务校验令牌合法性,完成授权
    • 6、资源服务器返回受保护资源
  • 申请授权码
    • 启动认证服务器
    • get请求获取授权码:localhost:40400/auth/oauth/authorize?client_id=XcWebApp&response_type=code&scop=app&redirect_uri=http://localhost
    • 输入用户名密码登录
      在这里插入图片描述
    • Spring Security接收到请求会调用UserDetailsService接口的loadUserByUsername方法查询用户正确的密码。
    • 进入授权页面
      在这里插入图片描述
    • 返回授权码,认证服务携带授权码跳转redirect_uri
      在这里插入图片描述
  • 申请令牌
    • POST请求申请令牌:http://localhost:40400/auth/oauth/token
      • 参数
        • grant_type:授权类型,填写authorization_code,表示授权码模式
        • code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。
        • redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。
    • 使用 http Basic认证。
      • http协议定义的一种认证方式,将客户端id和客户端密码按照“客户端ID:客户端密码”的格式拼接,并用base64编码,放在header中请求服务端。
    • post请求
      在这里插入图片描述
    • 申请令牌成功
      在这里插入图片描述
  • 资源服务授权
    • 资源服务授权流程
      在这里插入图片描述
      • (1)客户端请求认证服务申请令牌
      • (2)认证服务生成令牌:认证服务采用非对称加密算法,使用私钥生成令牌
      • (3)客户端携带令牌访问资源服务:客户端在http header中添加:Authorization : Bearer令牌
      • (4)资源服务请求认证服务校验令牌的有效性:使用公钥校验
      • (5)令牌有效,资源服务向客户端响应资源信息
    • 资源服务授权配置
      • 配置公钥
      • 添加依赖
      	<dependency>
      		<groupId>org.springframework.cloud</groupId>
      		<artifactId>spring‐cloud‐starter‐oauth2</artifactId>
      	</dependency>
      
      • 在config包下创建ResourceServerConfig类
      @Configuration
      @EnableResourceServer
      @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的
      PreAuthorize注解
      public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
      	//公钥
      	private static final String PUBLIC_KEY = "publickey.txt";
      	//定义JwtTokenStore,使用jwt令牌
      	@Bean
      	public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
      		return new JwtTokenStore(jwtAccessTokenConverter);
      	}
      	//定义JJwtAccessTokenConverter,使用jwt令牌
      	@Bean
      	public JwtAccessTokenConverter jwtAccessTokenConverter() {
      		JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
      		converter.setVerifierKey(getPubKey());
      		return converter;
      	}
      	/**
      	* 获取非对称加密公钥 Key
      	* @return 公钥 Key
      	*/
      	private String getPubKey() {
      	Resource resource = new ClassPathResource(PUBLIC_KEY);
      	try {
      			InputStreamReader inputStreamReader = new
      			InputStreamReader(resource.getInputStream());
      			BufferedReader br = new BufferedReader(inputStreamReader);
      			return br.lines().collect(Collectors.joining("\n"));
      		} catch (IOException ioe) {
      			return null;
      		}
      	}
      	//Http安全配置,对每个到达系统的http请求链接进行校验
      	@Override
      	public void configure(HttpSecurity http) throws Exception {
      	//所有请求必须认证通过
      	http.authorizeRequests().anyRequest().authenticated();
      	}
      }
      

Oauth2密码模式授权

  • 密码模式(Resource Owner Password Credentials)与授权码模式的区别是申请令牌不再使用授权码,而是直接通过用户名和密码即可申请令牌。
  • post请求:http://localhost:40400/auth/oauth/token
    • 参数:
      • grant_type:密码模式授权填写password
      • username:账号
      • password:密码
      • 使用 http Basic认证。
      • 参数使用x-www-form-urlencoded方式传输
    • 当令牌没有过期时同一个用户再次申请令牌则不再颁发新令牌。
      在这里插入图片描述

检验令牌

  • get请求:http://localhost:40400/auth/oauth/check_token?token=
  • 参数
    • token:令牌
  • 测试结果
    在这里插入图片描述

刷新令牌

  • 刷新令牌是当令牌快过期时重新生成一个令牌,它于授权码授权和密码授权生成令牌不同,刷新令牌不需要授权码也不需要账号和密码,只需要一个刷新令牌、客户端id和客户端密码。
  • post请求:http://localhost:40400/auth/oauth/token
    • grant_type: 固定为 refresh_token
    • refresh_token:刷新令牌(注意不是access_token,而是refresh_token)
  • 测试结果
    在这里插入图片描述

JWT

  • 传统校验令牌方法
    在这里插入图片描述
    • 传统授权方法的问题是用户每次请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的合法性,并根据令牌获取用户的相关信息,性能低下。
    • 解决:使用JWT的思路是,用户认证通过会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。
  • JWT令牌授权过程
    在这里插入图片描述
  • JWT
    • JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。
    • JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
  • JWT令牌的优点
    • (1)jwt基于json,非常方便解析。
    • (2)可以在令牌中自定义丰富的内容,易扩展。
    • (3)通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高
    • (4)资源服务使用JWT可不依赖认证服务即可完成授权。
  • JWT缺点
    • 令牌较长,占存储空间较大
  • 令牌结构
    • JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz
    • Header
      • 头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
      • 将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。
    • Payload
      • 第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
      • 此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
      • 第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
    • Signature
      • 第三部分是签名,此部分用于防止jwt内容被篡改。
      • 这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。
  • JWT操作
    • 生成私钥和公钥
      • 生成密钥证书
      keytool -genkeypair -alias xckey -keyalg RSA -keypass xuecheng -keystore xc.keystore -storepass xuechengkeystore
      
        * Keytool 是一个java提供的证书管理工具
        * -alias:密钥的别名
        * -keyalg:使用的hash算法
        * -keypass:密钥的访问密码
        * -keystore:密钥库文件名,xc.keystore保存了生成的证书
        * -storepass:密钥库的访问密码
      
      • 导出公钥
        • 使用openssl来导出公钥信息
        • cmd进入xc.keystore文件所在目录执行如下命令:
        keytool ‐list ‐rfc ‐‐keystore xc.keystore | openssl x509 ‐inform pem ‐pubkey
        
        • 输入密钥库密码
          在这里插入图片描述
      • public key即为公钥内容,将上边的公钥拷贝到文本文件中,合并为一行。
    • 生成jwt令牌
    //生成一个jwt令牌
    @Test
    public void testCreateJwt(){
    	//证书文件
    	String key_location = "xc.keystore";
    	//密钥库密码
    	String keystore_password = "xuechengkeystore";
    	//访问证书路径
    	ClassPathResource resource = new ClassPathResource(key_location);
    	//密钥工厂
    	KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource,
    	keystore_password.toCharArray());
    	//密钥的密码,此密码和别名要匹配
    	String keypassword = "xuecheng";
    	//密钥别名
    	String alias = "xckey";
    	//密钥对(密钥和公钥)
    	KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias,keypassword.toCharArray());
    	//私钥
    	RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();
    	//定义payload信息
    	Map<String, Object> tokenMap = new HashMap<>();
    	tokenMap.put("id", "123");
    	tokenMap.put("name", "mrt");
    	tokenMap.put("roles", "r01,r02");
    	tokenMap.put("ext", "1");
    	//生成jwt令牌
    	Jwt jwt = JwtHelper.encode(JSON.toJSONString(tokenMap), new RsaSigner(aPrivate));
    	//取出jwt令牌
    	String token = jwt.getEncoded();
    	System.out.println("token="+token);
    }
    
    • 验证jwt令牌
    //资源服务使用公钥验证jwt的合法性,并对jwt解码
    @Test
    public void testVerify(){
    	//jwt令牌
    	String token
    	="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHQiOiIxIiwicm9sZXMiOiJyMDEscjAyIiwibmFtZSI6Im1ydCIsI
    	mlkIjoiMTIzIn0.KK7_67N5d1Dthd1PgDHMsbi0UlmjGRcm_XJUUwseJ2eZyJJWoPP2IcEZgAU3tUaaKEHUf9wSRwaDgwhrw
    	fyIcSHbs8oy3zOQEL8j5AOjzBBs7vnRmB7DbSaQD7eJiQVJOXO1QpdmEFgjhc_IBCVTJCVWgZw60IEW1_Lg5tqaLvCiIl26K
    	48pJB5f‐le2zgYMzqR1L2LyTFkq39rG57VOqqSCi3dapsZQd4ctq95SJCXgGdrUDWtD52rp5o6_0uqmrbRdRxkrQfsa1j8C5IW2‐
    	T4eUmiN3f9wF9JxUK1__XC1OQkOn‐ZTBCdqwWIygDFbU7sf6KzfHJTm5vfjp6NIA";
    	//公钥
    	String publickey = "‐‐‐‐‐BEGIN PUBLIC KEY‐‐‐‐‐
    	MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAijyxMdq4S6L1Af1rtB8SjCZHNgsQG8JTfGy55eYvzG0B/E4AudR2
    	prSRBvF7NYPL47scRCNPgLnvbQczBHbBug6uOr78qnWsYxHlW6Aa5dI5NsmOD4DLtSw8eX0hFyK5Fj6ScYOSFBz9cd1nNTvx
    	2+oIv0lJDcpQdQhsfgsEr1ntvWterZt/8r7xNN83gHYuZ6TM5MYvjQNBc5qC7Krs9wM7UoQuL+s0X6RlOib7/mcLn/lFLsLD
    	dYQAZkSDx/6+t+1oHdMarChIPYT1sx9Dwj2j2mvFNDTKKKKAq0cv14Vrhz67Vjmz2yMJePDqUi0JYS2r0iIo7n8vN7s83v5u
    	OQIDAQAB‐‐‐‐‐END PUBLIC KEY‐‐‐‐‐";
    	//校验jwt
    	Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publickey));
    	//获取jwt原始内容
    	String claims = jwt.getClaims();
    	//jwt令牌
    	String encoded = jwt.getEncoded();
    	System.out.println(encoded);
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值