Java单点登录基础实现(jwt+rsa)

分布式单点登录

介绍:

单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统

登录方式的分类:

按用户状态分:

  • 有状态登录
    服务器需要保存用户的信息,登录成功后将用户存在session中,通过cookie保存Jsessionid,下次访问携带id,获得服务器中的用户信息。
    问题:session只能保存再一个服务器中,其他服务访问需要再次登录。
  • 无状态登录
    将信息保存到客户端(使用cookie)
    好处:客户端访问时,服务器读取用户信息,服务器的负担小了。
    缺点:安全性差。

单点登录实现(JWT+RSA)

JWT:是为了网络应用环境间传递说明而执行的一种基于json的开发标准。该token被设计紧凑而安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密

官方图解:
在这里插入图片描述

加密方式:

1)对称式加密:
加密和解密只需要一个密钥
特点:算法公开,计算量小,加密速度快,效率高。
缺点:安全性低
主要算法:des,sdes,tdea,rc5,idea

2)非对称式加密:
分为公钥和私钥
私钥加密:公钥私钥均可解
公钥加密:只有私钥可解
特点:算法复杂,加密速度慢,但是安全性会高很多。
主要算法:rsa(椭圆曲线加密算法)

具体项目中使用:

第一个工具类:CookieUtil 主要可以对cookie进行设置和存值

package com.cloud.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * Cookie 工具类 得到cookie中的值或者设置cookie内容
 *
 */
public final class CookieUtil {

	public static final String COOKIE_NAME = "token"; //cookie名称
	public static final int COOKIE_MAX_AGE = 1800; //cookie生命周期


	static final Logger logger = LoggerFactory.getLogger(CookieUtil.class);

	/**
	 * 得到Cookie的值, 不编码
	 *
	 * @param request
	 * @param cookieName
	 * @return
	 */
	public static String getCookieValue(HttpServletRequest request, String cookieName) {
		return getCookieValue(request, cookieName, false);
	}

	/**
	 * 得到Cookie的值,
	 *
	 * @param request
	 * @param cookieName
	 * @return
	 */
	public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
		Cookie[] cookieList = request.getCookies();
		return getCookieValue(cookieList,cookieName,isDecoder);
	}

	public static String getCookieValue(Cookie[] cookieList, String cookieName, boolean isDecoder) {
		if (cookieList == null || cookieName == null){
			return null;
		}
		String retValue = null;
		try {
			for (int i = 0; i < cookieList.length; i++) {
				if (cookieList[i].getName().equals(cookieName)) {
					if (isDecoder) {
						retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
					} else {
						retValue = cookieList[i].getValue();
					}
					break;
				}
			}
		} catch (UnsupportedEncodingException e) {
			logger.error("Cookie Decode Error.", e);
		}
		return retValue;
	}

	/**
	 * 得到Cookie的值,
	 *
	 * @param request
	 * @param cookieName
	 * @return
	 */
	public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
		Cookie[] cookieList = request.getCookies();
		if (cookieList == null || cookieName == null){
			return null;
		}
		String retValue = null;
		try {
			for (int i = 0; i < cookieList.length; i++) {
				if (cookieList[i].getName().equals(cookieName)) {
					retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
					break;
				}
			}
		} catch (UnsupportedEncodingException e) {
			logger.error("Cookie Decode Error.", e);
		}
		return retValue;
	}

	/**
	 * 生成cookie,并指定编码
	 * @param request 请求
	 * @param response 响应
	 * @param cookieName name
	 * @param cookieValue value
	 * @param encodeString 编码
	 */
	public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, String encodeString) {
		setCookie(request,response,cookieName,cookieValue,null,encodeString, null);
	}

	/**
	 * 生成cookie,并指定生存时间
	 * @param request 请求
	 * @param response 响应
	 * @param cookieName name
	 * @param cookieValue value
	 * @param cookieMaxAge 生存时间
	 */
	public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, Integer cookieMaxAge) {
		setCookie(request,response,cookieName,cookieValue,cookieMaxAge,null, null);
	}

	/**
	 * 设置cookie,不指定httpOnly属性
	 */
	public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, Integer cookieMaxAge, String encodeString) {
		setCookie(request,response,cookieName,cookieValue,cookieMaxAge,encodeString, null);
	}

	/**
	 * 设置Cookie的值,并使其在指定时间内生效
	 *
	 * @param cookieMaxAge
	 *            cookie生效的最大秒数
	 */
	public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, Integer cookieMaxAge, String encodeString, Boolean httpOnly) {
		try {
			if(StringUtils.isEmpty(encodeString)) {
				encodeString = "utf-8";
			}

			if (cookieValue == null) {
				cookieValue = "";
			} else {
				cookieValue = URLEncoder.encode(cookieValue, encodeString);
			}
			Cookie cookie = new Cookie(cookieName, cookieValue);
			if (cookieMaxAge != null && cookieMaxAge > 0)
				cookie.setMaxAge(cookieMaxAge);
//			if (null != request)// 设置域名的cookie
//			{
//				String domainName = getDomainName(request);
//				System.out.println("domain:" +domainName);
//				cookie.setDomain(domainName);
//			}
			cookie.setPath("/");

			if(httpOnly != null) {
				cookie.setHttpOnly(httpOnly);
			}
			response.addCookie(cookie);
		} catch (Exception e) {
			logger.error("Cookie Encode Error.", e);
		}
	}

	/**
	 * 得到cookie的域名
	 */
	private static final String getDomainName(HttpServletRequest request) {
		String domainName = null;

		String serverName = request.getRequestURL().toString();
		if (serverName == null || serverName.equals("")) {
			domainName = "";
		} else {
			serverName = serverName.toLowerCase();
			serverName = serverName.substring(7);
			final int end = serverName.indexOf("/");
			serverName = serverName.substring(0, end);
			final String[] domains = serverName.split("\\.");
			int len = domains.length;
			if (len > 3) {
				// www.xxx.com.cn
				domainName = domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
			} else if (len <= 3 && len > 1) {
				// xxx.com or xxx.cn
				domainName = domains[len - 2] + "." + domains[len - 1];
			} else {
				domainName = serverName;
			}
		}

		if (domainName != null && domainName.indexOf(":") > 0) {
			String[] ary = domainName.split("\\:");
			domainName = ary[0];
		}
		return domainName;
	}

}

第二个工具类:JwtUtil 主要功能,加密获得token字符串,解密获得token中的用户信息,

package com.cloud.utils;


import com.cloud.entity.TbUser;
import com.cloud.entity.UserInfo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;

import java.security.PrivateKey;
import java.security.PublicKey;


/**
 * @author yanglihu‘
 * 进行加密 和 解析token功能
 */
public class JwtUtil {

    public static final String JWT_KEY_USERNAME = "username";
    public static final String JWT_KEY_PASSWORD = "password";
    public static final String JWT_KEY_AUTHORITIES = "authorities";
    public static final int EXPIRE_MINUTES = 30;

    /**
     * 私钥加密token
     */
    public static String generateToken(String username,String password,String authorities, PrivateKey privateKey, int expireMinutes) throws Exception {
        return Jwts.builder()
                .claim(JWT_KEY_USERNAME, username)
                .claim(JWT_KEY_PASSWORD, password)
                .claim(JWT_KEY_AUTHORITIES, authorities)
                .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                .signWith(SignatureAlgorithm.RS256, privateKey)
                .compact();
    }

    /**
     * 从token解析用户
     * @param token
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static UserInfo getUserInfoFromToken(String token, PublicKey publicKey) throws Exception {
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
        Claims body = claimsJws.getBody();
        String username = (String) body.get(JWT_KEY_USERNAME);
        UserInfo user = new UserInfo();
        user.setUsername(username);
        return user;
    }
}

第三个工具类:RsaUtil 主要功能创建密钥及对密钥相关操作

package com.cloud.utils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @author yanglihu
 * 设置密钥的位置以及对密钥的相关操作
 */
public class RsaUtil {

    public static final String RSA_SECRET = "java1130@#$%"; //秘钥
    public static final String RSA_PATH = "D:\\rsa\\";//秘钥保存位置
    public static final String RSA_PUB_KEY_PATH = RSA_PATH + "pub.rsa";//公钥路径
    public static final String RSA_PRI_KEY_PATH = RSA_PATH + "pri.rsa";//私钥路径

    public static PublicKey publicKey;
    public static PrivateKey privateKey;

    static{
        try {
            File rsa = new File(RSA_PATH);
            if(!rsa.exists()){
                rsa.mkdirs();
            }
            File pubKey = new File(RSA_PUB_KEY_PATH);
            File priKey = new File(RSA_PRI_KEY_PATH);
            //判断公钥和私钥如果不存在就创建
            if (!priKey.exists() || !pubKey.exists()) {
                //创建公钥和私钥文件
                RsaUtil.generateKey(RSA_PUB_KEY_PATH, RSA_PRI_KEY_PATH, RSA_SECRET);
            }
            //读取公钥和私钥内容
            publicKey = RsaUtil.getPublicKey(RSA_PUB_KEY_PATH);
            privateKey = RsaUtil.getPrivateKey(RSA_PRI_KEY_PATH);
        }catch (Exception ex){
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
    }

    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径,相对于classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(byte[] bytes) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}

鉴权服务中:对springscurity的用户登录验证成功处理器加密生成token

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
        User user = (User) authentication.getPrincipal();

        //将用户名转化为jwt 保存到cookie中
        try {
            String token = JwtUtil.generateToken(user.getUsername(), "", "", RsaUtil.privateKey, JwtUtil.EXPIRE_MINUTES);
            CookieUtil.setCookie(req,resp,CookieUtil.COOKIE_NAME,token,CookieUtil.COOKIE_MAX_AGE);
        } catch (Exception e) {
            e.printStackTrace();
        }

        resp.setContentType("application/json;charset=utf-8");
        PrintWriter out = resp.getWriter();
        //jackson转换为json格式字符串
        out.write(new ObjectMapper().writeValueAsString(user));
        out.flush();
        out.close();
    }
}

网关服务中:对于token的解密

@Component
@Slf4j
public class MyGatewayAuthenticationFilter implements GlobalFilter, Ordered {
    /**
     * 白名单
     */
    private static final  String[] WHITE_LIST={"/login","/logout"};

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //转换对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //验证请求是否在白名单中
        for (String str:WHITE_LIST){
            if (request.getURI().getPath().endsWith(str)){
                return chain.filter(exchange);
            }
        }
        try {
            //获得请求中的 token参数
            String token = request.getCookies().getFirst("token").getValue();
            //解读token 获得对象
            UserInfo user = JwtUtil.getUserInfoFromToken(token, RsaUtil.publicKey);
        } catch (Exception e) {
            e.printStackTrace();
            //验证失败返回信息
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            String msg="request denied";
            DataBuffer wrap = response.bufferFactory().wrap(msg.getBytes());
            return response.writeWith(Mono.just(wrap));
        }
        return chain.filter(exchange);
    }

    /**
     * 返回值 拦截器顺序  越小越靠前
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
单点登录(Single Sign-On,简称SSO)是一种身份验证技术,可以让用户只需一次登录,就可以访问多个应用程序。在实际开发中,我们可以使用Spring Boot、JWT和Redis来实现单点登录功能。 下面是实现单点登录的步骤: 1. 创建Spring Boot项目并引入所需依赖:spring-boot-starter-web、spring-boot-starter-data-redis和jjwt。 2. 创建一个User实体类,包含用户名和密码等信息。 3. 创建一个UserService,实现对用户信息的操作,包括注册、登录等。 4. 引入JWT依赖后,我们需要创建一个JWTUtil类,实现token的生成和解析。 5. 创建一个LoginController,用于处理用户的登录请求。在登录成功后,生成token并将其存储到Redis中。 6. 创建一个AuthController,用于验证用户的token是否有效。在验证成功后,可以获取用户信息并返回。 7. 在需要进行单点登录验证的应用程序中,只需要在请求中携带token,并调用AuthController进行验证即可。 具体实现细节可以参考以下代码示例: User实体类: ```java public class User { private String username; private String password; // 省略setter和getter方法 } ``` UserService接口: ```java public interface UserService { void register(User user); String login(String username, String password); } ``` UserService实现类: ```java @Service public class UserServiceImpl implements UserService { @Autowired private RedisTemplate<String, String> redisTemplate; @Override public void register(User user) { // 省略用户注册逻辑 } @Override public String login(String username, String password) { // 省略用户登录逻辑 // 登录成功后生成token并存储到Redis中 String token = JWTUtil.generateToken(username); redisTemplate.opsForValue().set(username, token, 30, TimeUnit.MINUTES); return token; } } ``` JWTUtil类: ```java public class JWTUtil { private static final String SECRET_KEY = "my_secret_key"; private static final long EXPIRATION_TIME = 3600000; public static String generateToken(String username) { return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static String getUsernameFromToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody() .getSubject(); } } ``` LoginController: ```java @RestController public class LoginController { @Autowired private UserService userService; @PostMapping("/login") public ResponseEntity<String> login(@RequestBody User user) { String token = userService.login(user.getUsername(), user.getPassword()); return ResponseEntity.ok(token); } } ``` AuthController: ```java @RestController public class AuthController { @Autowired private RedisTemplate<String, String> redisTemplate; @GetMapping("/auth") public ResponseEntity<User> auth(@RequestHeader("Authorization") String token) { String username = JWTUtil.getUsernameFromToken(token); if (StringUtils.isEmpty(username)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } String redisToken = redisTemplate.opsForValue().get(username); if (!token.equals(redisToken)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } User user = new User(); user.setUsername(username); return ResponseEntity.ok(user); } } ``` 在请求中携带token的示例: ```java @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setInterceptors(Collections.singletonList((request, body, execution) -> { String token = // 从Redis中获取token request.getHeaders().add("Authorization", token); return execution.execute(request, body); })); return restTemplate; } } ``` 以上就是使用Spring Boot、JWT和Redis实现单点登录的步骤和示例代码。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

记或往

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值