最新文章
springboot集成Security使用token实现认证授权(二) 认证授权
......接上文“springboot集成Security使用token实现认证授权(一) 准备篇”security的认证授权配置类:@Configuration//开启security@EnableWebSecurity//开启方法级注解支持@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserService userService; @Autowired private RsaProperties rsaProperties; /** * 加密对象放入ioc容器 * * @return */ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } /** * 加密策略 * * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder()); } /** * 全新啊配置 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() //.antMatchers().permitAll() //需认证后访问的方法 .anyRequest() .authenticated() .and() //认证过滤器 .addFilter(new UserAuthenticationFilter(authenticationManager(), rsaProperties)) //token验证过滤器 .addFilter(new TokenVerifyFilter(authenticationManager(), rsaProperties)) //禁用session .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() //关闭csrf .csrf().disable() .cors().disable() ; }} 认证过滤器:public class UserAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; private RsaProperties rsaProperties; public UserAuthenticationFilter(AuthenticationManager authenticationManager, RsaProperties rsaProperties) { this.authenticationManager = authenticationManager; this.rsaProperties = rsaProperties; } /** * 获取用户名密码 * * @param request * @param response * @return * @throws AuthenticationException */ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { UserPo userPo = null; try { //获取用户信息 userPo = new ObjectMapper().readValue(request.getInputStream(), UserPo.class); //认证 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userPo.getUsername(), userPo.getPassword()); Authentication authentication = authenticationManager.authenticate(authRequest); return authentication; } catch (IOException e) { DataToHttpServletResponseUtil.msgToResponseWhenSuccess(response, JsonResponseUtil.fail(ResponseCodeAndMsgEnum.FAILE.getCode(), "用户名或密码错误")); } throw new BaseException("登录异常"); } /** * 用户名密码授权成功返货token * * @param request * @param response * @param chain * @param authResult * @throws IOException * @throws ServletException */ @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { //从authResult获取认证成功的用户信息 UserPo authUser = (UserPo) authResult.getPrincipal(); authUser.setPassword(null); //生成token String token = JwtUtil.generateTokenExpireInMinutes(authUser, rsaProperties.getPrivateKey(), 10); //将token写入header response.addHeader("Authorization", "Bearer " + token); DataToHttpServletResponseUtil.msgToResponseWhenSuccess(response, JsonResponseUtil.success(ResponseCodeAndMsgEnum.SUCCESS.getCode(), "登录成功")); } /** * 认证失败 * * @param request * @param response * @param failed * @throws IOException * @throws ServletException */ @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { DataToHttpServletResponseUtil.msgToResponseWhenSuccess(response, JsonResponseUtil.fail(ResponseCodeAndMsgEnum.FAILE.getCode(), "用户名或密码错误")); }} token检测过滤器:public class TokenVerifyFilter extends BasicAuthenticationFilter { private RsaProperties rsaProperties; public TokenVerifyFilter(AuthenticationManager authenticationManager, RsaProperties rsaProperties) { super(authenticationManager); this.rsaProperties = rsaProperties; } /** * 过滤请求 * * @param request * @param response * @param chain */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { try { //请求体的头中是否包含Authorization String header = request.getHeader("Authorization"); //Authorization中是否包含Bearer,不包含直接返回 if (header == null || !header.startsWith("Bearer ")) { chain.doFilter(request, response); DataToHttpServletResponseUtil.msgToResponseWhenSuccess(response, JsonResponseUtil.fail(ResponseCodeAndMsgEnum.FAILE.getCode(), "请登录!")); return; } //获取权限失败,会抛出异常 UsernamePasswordAuthenticationToken authentication = getAuthentication(request); //获取后,将Authentication写入SecurityContextHolder中供后序使用 SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } catch (Exception e) { DataToHttpServletResponseUtil.msgToResponseWhenSuccess(response, JsonResponseUtil.fail(ResponseCodeAndMsgEnum.FAILE.getCode(), "请登录!")); e.printStackTrace(); } } /** * 通过token,获取用户信息 * * @param request * @return */ private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { String token = request.getHeader("Authorization"); if (token != null) { //通过token解析出载荷信息 Payload<UserPo> payload = JwtUtil.getInfoFromToken(token.replace("Bearer ", ""), rsaProperties.getPublicKey(), UserPo.class); UserPo user = payload.getUserInfo(); //不为null,返回 if (user != null) { return new UsernamePasswordAuthenticationToken(user, null, user.getRoleList()); } return null; } return null; }} 编写controller的测试请求:@RestController@RequestMapping("/orderTest")public class OrderController { //拥有ROLE_ORDER角色才能访问 @PreAuthorize(value = "hasAnyRole('ROLE_ORDER')") @PostMapping("/list") public String list() { return "订单列表查询成功"; }} @RestController@RequestMapping("/productTest")public class ProductController {//拥有ROLE_PRODUCT角色才能访问 @PreAuthorize(value = "hasAnyRole('ROLE_PRODUCT')") @GetMapping("/list") public String list(){ return "产品列表访问成功"; }//登录即可访问 @GetMapping("/detail") public String detail(){ return "产品详情访问成功"; }} 测试我们使用postman进行测试,启动项目后调用login请求(login请求是security自带的,不需要我们编写)登录,登录成功后在响应的header中获取token。如图:访问其他需要认证的接口时需要将token放入请求头中。如图:如果登录用户没有该接口的访问权限,程序将出现异常,提示你不允许访问。而像访问/productTest/detail这样的接口就能正常访问。 本文主要在于演示security如何实现认证授权,如果对文中代码有疑问或者有什么建议的同学欢迎留言或添加好友交流。
Apr 15, 2020 10:55:04 PM
401
springboot集成Security使用token实现认证授权(一) 准备篇
springsecurity是企业中经常用到的安全控制框架,为我们提供认证授权的功能,由于security与sping无缝对接,让程序员的开发简单灵活。本文将简单地演示一下springsecurity在springboot项目中如何实现基于token的认证授权。本文主要参考博客: https://www.cnblogs.com/ifme/p/12184587.htmlRBACRBAC是基于角色的访问控制(Role-Based Access Control )。在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。相当于权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。本文主要讲解通过角色来控制用户访问方法的权限。建表语句SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`permission_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`permission_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限名称',
`permission_desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限描述',
PRIMARY KEY (`permission_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`role_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称',
`role_desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色说明',
INDEX `role_id`(`role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_role_permission_auth
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission_auth`;
CREATE TABLE `sys_role_permission_auth` (
`role_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色id',
`permission_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限id',
UNIQUE INDEX `role_id`(`role_id`, `permission_id`) USING BTREE,
INDEX `permission_id`(`permission_id`) USING BTREE,
CONSTRAINT `sys_role_permission_auth_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `sys_role_permission_auth_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `sys_permission` (`permission_id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色权限关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`user_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
`state` int(2) NOT NULL COMMENT '状态。1正常,2删除',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_user_role_auth
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role_auth`;
CREATE TABLE `sys_user_role_auth` (
`user_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户',
`role_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色',
UNIQUE INDEX `user_id`(`user_id`, `role_id`) USING BTREE,
INDEX `role_id`(`role_id`) USING BTREE,
CONSTRAINT `sys_user_role_auth_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`user_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `sys_user_role_auth_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色关系表' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1; 代码实现搭建maven工程pom依赖<!-- 安全框架 --><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId></dependency><!-- 由于后面要用springcloud继续优化,所以选择了cloud-stater --><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId></dependency><!-- jwt --><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.10.7</version></dependency><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.10.7</version> <scope>runtime</scope></dependency><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.10.7</version> <scope>runtime</scope></dependency><!-- 获取配置信息 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional></dependency><!-- lombok插件 --><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional></dependency><!-- 时间工具包 --><dependency> <groupId>com.bluecatcode.time</groupId> <artifactId>joda-time-2.1-extended</artifactId> <version>1.1.0</version></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><!-- mybatis --><dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version></dependency><!-- mysql连接工具 --><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope></dependency> 启动类@SpringBootApplication//扫描dao接口@MapperScan(basePackages = {"work.chenchuan.providertestone.**.dao"})//开启事务管理@EnableTransactionManagementpublic class ProviderTestOneApplication { public static void main(String[] args) { SpringApplication.run(ProviderTestOneApplication.class, args); }} 导入工具类json工具类:public class JsonUtil { public static final ObjectMapper mapper = new ObjectMapper(); private static final Logger logger = LoggerFactory.getLogger(JsonUtil.class); public static String toString(Object obj) { if (obj == null) { return null; } if (obj.getClass() == String.class) { return (String) obj; } try { return mapper.writeValueAsString(obj); } catch (JsonProcessingException e) { logger.error("json序列化出错:" + obj, e); return null; } } public static <T> T toBean(String json, Class<T> tClass) { try { return mapper.readValue(json, tClass); } catch (IOException e) { logger.error("json解析出错:" + json, e); return null; } } public static <E> List<E> toList(String json, Class<E> eClass) { try { return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass)); } catch (IOException e) { logger.error("json解析出错:" + json, e); return null; } } public static <K, V> Map<K, V> toMap(String json, Class<K> kClass, Class<V> vClass) { try { return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass)); } catch (IOException e) { logger.error("json解析出错:" + json, e); return null; } } public static <T> T nativeRead(String json, TypeReference<T> type) { try { return mapper.readValue(json, type); } catch (IOException e) { logger.error("json解析出错:" + json, e); return null; } }} jwt工具类:public class JwtUtil { private static final String JWT_PAYLOAD_USER_KEY = "user"; /** * 私钥加密token * * @param userInfo 载荷中的数据 * @param privateKey 私钥 * @param expire 过期时间,单位分钟 * @return JWT */ public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) { return Jwts.builder() .claim(JWT_PAYLOAD_USER_KEY, JsonUtil.toString(userInfo)) .setId(createJTI()) .setExpiration(DateTime.now().plusMinutes(expire).toDate()) .signWith(privateKey, SignatureAlgorithm.RS256) .compact(); } /** * 私钥加密token * * @param userInfo 载荷中的数据 * @param privateKey 私钥 * @param expire 过期时间,单位秒 * @return JWT */ public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) { return Jwts.builder() .claim(JWT_PAYLOAD_USER_KEY, JsonUtil.toString(userInfo)) .setId(createJTI()) .setExpiration(DateTime.now().plusSeconds(expire).toDate()) .signWith(privateKey, SignatureAlgorithm.RS256) .compact(); } /** * 公钥解析token * * @param token 用户请求中的token * @param publicKey 公钥 * @return Jws<Claims> */ private static Jws<Claims> parserToken(String token, PublicKey publicKey) { return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token); } private static String createJTI() { return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes())); } /** * 获取token中的用户信息 * * @param token 用户请求中的令牌 * @param publicKey 公钥 * @return 用户信息 */ public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) { Jws<Claims> claimsJws = parserToken(token, publicKey); Claims body = claimsJws.getBody(); Payload<T> claims = new Payload<>(); claims.setId(body.getId()); claims.setUserInfo(JsonUtil.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType)); claims.setExpiration(body.getExpiration()); return claims; } /** * 获取token中的载荷信息 * * @param token 用户请求中的令牌 * @param publicKey 公钥 * @return 用户信息 */ public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) { Jws<Claims> claimsJws = parserToken(token, publicKey); Claims body = claimsJws.getBody(); Payload<T> claims = new Payload<>(); claims.setId(body.getId()); claims.setExpiration(body.getExpiration()); return claims; }} rsa工具类:public class RsaUtil { private static final int DEFAULT_KEY_SIZE = 2048; /** * 从文件中读取公钥 * * @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 */ private static PublicKey getPublicKey(byte[] bytes) throws Exception { bytes = Base64.getDecoder().decode(bytes); X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePublic(spec); } /** * 获取密钥 * * @param bytes 私钥的字节形式 * @return * @throws Exception */ private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException { bytes = Base64.getDecoder().decode(bytes); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(spec); } /** * 根据密文,生存rsa公钥和私钥,并写入指定文件 * * @param publicKeyFilename 公钥文件路径 * @param privateKeyFilename 私钥文件路径 * @param secret 生成密钥的密文 */ public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); SecureRandom secureRandom = new SecureRandom(secret.getBytes()); keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom); KeyPair keyPair = keyPairGenerator.genKeyPair(); // 获取公钥并写出 byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes); writeFile(publicKeyFilename, publicKeyBytes); // 获取私钥并写出 byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes); 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); }} 响应状态码枚举,定于程序响应状态的状态码和状态说明:public enum ResponseCodeAndMsgEnum { SUCCESS("200", "请求成功"), FAILE("-1", "请求失败"), SYS_ERROR("500", "服务器内部错误"), UNAUTHORIZED("401", "没有权限访问该资源"), //FORBIDDEN("403", "没有权限访问该资源"), NOT_FOUND("404", "访问的资源不存在"), SERVICE_UNAVAILABLE("503","相关服务器异常,触发熔断"); /** * 状态码 */ private String code; /** * 消息提示 */ private String msg; ResponseCodeAndMsgEnum(String code, String msg) { this.code = code; this.msg = msg; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; }} controller格式统一返回的属性:public class JsonResult<T> { /** * 访问状态 */ private Boolean state; /** * 响应状态码 */ private String code; /** * 响应提示信息 */ private String msg; /** * 返回数据 */ private T data; /** * 请求路径 */ private String uri; /** * 当前时间 */ private Long timestamp; public Boolean getState() { return state; } public void setState(Boolean state) { this.state = state; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public Long getTimestamp() { return timestamp; } public void setTimestamp(Long timestamp) { this.timestamp = timestamp; }} controller统一格式返回工具类:public class JsonResponseUtil { /** * 请求成功默认 */ private static final ResponseCodeAndMsgEnum SUCCESS = ResponseCodeAndMsgEnum.SUCCESS; /** * 请求失败默认 */ private static final ResponseCodeAndMsgEnum FAILE = ResponseCodeAndMsgEnum.FAILE; /** * 系统服务错误映射 */ private static final ResponseCodeAndMsgEnum SYS_ERROR = ResponseCodeAndMsgEnum.SYS_ERROR; /** * 未授权映射 */ private static final ResponseCodeAndMsgEnum UNAUTHORIZED = ResponseCodeAndMsgEnum.UNAUTHORIZED; /** * 未找到资源映射 */ private static final ResponseCodeAndMsgEnum NOT_FOUND = ResponseCodeAndMsgEnum.NOT_FOUND; /** * 服务器由于维护或者负载过重未能应答或相关服务器异常,触发熔断 */ private static final ResponseCodeAndMsgEnum SERVICE_UNAVAILABLE = ResponseCodeAndMsgEnum.SERVICE_UNAVAILABLE; /** * 成功无数据——默认 * * @return json对象 */ public static JsonResult success() { JsonResult jsonResult = new JsonResult(); jsonResult.setState(true); jsonResult.setCode(SUCCESS.getCode()); jsonResult.setMsg(SUCCESS.getMsg()); jsonResult.setData(null); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 成功无数据——自定义code、msg * * @param code * @param msg * @return */ public static JsonResult success(String code, String msg) { JsonResult jsonResult = new JsonResult(); jsonResult.setState(true); jsonResult.setCode(code); jsonResult.setMsg(msg); jsonResult.setData(null); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 成功有数据——默认 * * @param o * @return */ public static JsonResult success(Object o) { JsonResult jsonResult = new JsonResult(); jsonResult.setState(true); jsonResult.setCode(SUCCESS.getCode()); jsonResult.setMsg(SUCCESS.getMsg()); jsonResult.setData(o); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 成功有数据——自定义code、msg * * @param code * @param msg * @param o * @return */ public static JsonResult success(String code, String msg, Object o) { JsonResult jsonResult = new JsonResult(); jsonResult.setState(true); jsonResult.setCode(code); jsonResult.setMsg(msg); jsonResult.setData(o); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 失败无数据——默认 * * @return */ public static JsonResult fail() { JsonResult jsonResult = new JsonResult(); jsonResult.setState(false); jsonResult.setCode(FAILE.getCode()); jsonResult.setMsg(FAILE.getMsg()); jsonResult.setData(null); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 失败无数据——自定义code、msg * * @param code * @param msg * @return */ public static JsonResult fail(String code, String msg) { JsonResult jsonResult = new JsonResult(); jsonResult.setState(false); jsonResult.setCode(code); jsonResult.setMsg(msg); jsonResult.setData(null); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 失败有数据——默认 * * @return */ public static JsonResult fail(Object o) { JsonResult jsonResult = new JsonResult(); jsonResult.setState(false); jsonResult.setCode(FAILE.getCode()); jsonResult.setMsg(FAILE.getMsg()); jsonResult.setData(o); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 失败有数据——自定义code、msg * * @param code * @param msg * @param o * @return */ public static JsonResult fail(String code, String msg, Object o) { JsonResult jsonResult = new JsonResult(); jsonResult.setState(false); jsonResult.setCode(code); jsonResult.setMsg(msg); jsonResult.setData(o); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 内部服务器错误 * * @return */ public static JsonResult sysError() { JsonResult jsonResult = new JsonResult(); jsonResult.setState(false); jsonResult.setCode(SYS_ERROR.getCode()); jsonResult.setMsg(SYS_ERROR.getMsg()); jsonResult.setData(null); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 没有权限访问资源 * * @return */ public static JsonResult unauthorized() { JsonResult jsonResult = new JsonResult(); jsonResult.setState(false); jsonResult.setCode(UNAUTHORIZED.getCode()); jsonResult.setMsg(UNAUTHORIZED.getMsg()); jsonResult.setData(null); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 资源不存在 * * @return */ public static JsonResult notFound() { JsonResult jsonResult = new JsonResult(); jsonResult.setState(false); jsonResult.setCode(NOT_FOUND.getCode()); jsonResult.setMsg(NOT_FOUND.getMsg()); jsonResult.setData(null); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 服务器由于维护或者负载过重未能应答或相关服务器异常,触发熔断 * ————默认 * * @return */ public static JsonResult unavailable() { JsonResult jsonResult = new JsonResult(); jsonResult.setState(false); jsonResult.setCode(SERVICE_UNAVAILABLE.getCode()); jsonResult.setMsg(SERVICE_UNAVAILABLE.getMsg()); jsonResult.setData(null); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; } /** * 服务器由于维护或者负载过重未能应答或相关服务器异常,触发熔断 * ————msg自定义 * * @param msg * @return */ public static JsonResult unavailable(String msg) { JsonResult jsonResult = new JsonResult(); jsonResult.setState(false); jsonResult.setCode(SERVICE_UNAVAILABLE.getCode()); jsonResult.setMsg(msg); jsonResult.setData(null); jsonResult.setTimestamp(new Date().getTime()); return jsonResult; }} 自定义基础异常类:public class BaseException extends RuntimeException { /** * 状态码 */ private String code; /** * 默认异常信息 * * @param jsonResult code、msg枚举 */ public BaseException(JsonResult jsonResult) { super(jsonResult.getMsg()); this.code = jsonResult.getCode(); } /** * 自定义msg * * @param message */ public BaseException(String message) { super(message); this.code = ResponseCodeAndMsgEnum.FAILE.getCode(); } /** * 自定义code、msg * * @param code 状态码 * @param msg 消息提示 */ public BaseException(String code, String msg) { super(msg); this.code = code; } public String getCode() { return code; } public void setCode(String code) { this.code = code; }} 全局异常捕获:@RestControllerAdvicepublic class GlobalExceptionHandler { /** * 自定义异常处理程序 * * @param request * @param e * @return */ @ExceptionHandler(value = RuntimeException.class) public JsonResult gloabalExceptionHandler(HttpServletRequest request, RuntimeException e) { e.printStackTrace(); //异常状态码和消息提示,初始化未定义的异常信息为500 ResponseCodeAndMsgEnum responseCodeAndMsgEnum = ResponseCodeAndMsgEnum.SYS_ERROR; String code = responseCodeAndMsgEnum.getCode(); String msg = responseCodeAndMsgEnum.getMsg(); //请求路径 String uri = request.getRequestURI(); //自定义异常code、msg if (e instanceof BaseException) { code = ((BaseException) e).getCode(); msg = e.getMessage(); } JsonResult jsonResult = JsonResponseUtil.fail(code, msg); jsonResult.setUri(uri); return jsonResult; }} 数据写入响应的工具类:public class DataToHttpServletResponseUtil { /** * 返回的统一json格式数据写入响应 * * @return */ public static void msgToResponseWhenSuccess(HttpServletResponse response, JsonResult jsonResult) { try { response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); PrintWriter out = response.getWriter(); out.write(new ObjectMapper().writeValueAsString(jsonResult)); out.flush(); out.close(); } catch (IOException e) { e.printStackTrace(); } }} 配置yml配置server: port: 9005spring: application: # 服务别名 name: provider-test-one # 数据源 datasource: url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncodeing=utf-8&serverTimezone=GMT%2b8 username: root password: 123mybatis: ## mybatis扫描xml路径 mapper-locations: classpath:providertestone/mybatis/mapper/**/*.xml# jwt、rsa加密rsa: key: private-key-file: D:\P\ideaProjects\mine\springcloud-frame-v1\jwtRsaKey\private.key public-key-file: D:\P\ideaProjects\mine\springcloud-frame-v1\jwtRsaKey\public.pub 分对称性加密配置@ConfigurationProperties(prefix = "rsa.key")@Component@Datapublic class RsaProperties { /** * 私秘钥文件 */ private String privateKeyFile; /** * 公钥文件 */ private String publicKeyFile; /** * 公钥 */ private PublicKey publicKey; /** * 私钥 */ private PrivateKey privateKey; @PostConstruct public void createRsaKey() throws Exception { publicKey = RsaUtil.getPublicKey(publicKeyFile); privateKey = RsaUtil.getPrivateKey(privateKeyFile); }} 实体类用户:@Datapublic class UserPo extends BasePo implements UserDetails { private String userId; private String username; private String password; /** * 状态 */ private Integer state; /** * 角色集合 */ private List<RolePo> roleList = new ArrayList<>(); @JsonIgnore @Override public Collection<? extends GrantedAuthority> getAuthorities() { return roleList; } /** * 账户是否过期 * @return */ @Override @JsonIgnore public boolean isAccountNonExpired() { return true; } /** * 账户是否锁定 * @return */ @Override @JsonIgnore public boolean isAccountNonLocked() { return true; } /** * 凭证是否可用 * @return */ @Override @JsonIgnore public boolean isCredentialsNonExpired() { return true; } /** * 账户是否启用 * @return */ @Override @JsonIgnore public boolean isEnabled() { return true; }} 角色:@Datapublic class RolePo extends BasePo implements GrantedAuthority { private String roleId; private String roleName; private String roleDesc; /** * 权限列表(本次暂时没有使用权限列表) */ private List<PermissionPo> permissionList = new ArrayList<>(); //标记此属性不做json处理 @JsonIgnore @Override public String getAuthority() { return roleName; }} 权限:@Datapublic class PermissionPo extends BasePo { private String permissionId; private String permissionName; private String permissionDesc;} 用户角色查询user的mapper文件,查询用户的角色:mapper对应的接口忽略。<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.2//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="work.chenchuan.providertestone.sys.dao.UserDao"> <sql id="userObjResult"> u.user_id AS userId, u.username AS username, u.password AS password, u.state AS state </sql> <!-- 用户权限集合 --> <resultMap id="userAndPermissionAuth" type="work.chenchuan.providertestone.sys.po.UserPo"> <id column="userId" property="userId"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="state" property="state"/> <collection property="roleList" ofType="work.chenchuan.providertestone.sys.po.RolePo"> <id column="roleId" property="roleId"/> <result column="roleName" property="roleName"/> <result column="roleDesc" property="roleDesc"/> <collection property="permissionList" ofType="work.chenchuan.providertestone.sys.po.PermissionPo"> <id column="permissionId" property="permissionId"/> <result column="permissionName" property="permissionName"/> <result column="permissionDesc" property="permissionDesc"/> </collection> </collection> </resultMap> <!-- 根据登录信息获取用户详情 --> <select id="getUserByLoginInfo" parameterType="work.chenchuan.providertestone.sys.po.UserPo" resultMap="userAndPermissionAuth"> SELECT <include refid="userObjResult"/>, <include refid="work.chenchuan.providertestone.sys.dao.RoleDao.roleObjResult"/>, <include refid="work.chenchuan.providertestone.sys.dao.PermissionDao.permissionObjResult"/> FROM `sys_user` u LEFT JOIN sys_user_role_auth ura ON u.user_id = ura.user_id LEFT JOIN sys_role r ON ura.role_id = r.role_id LEFT JOIN sys_role_permission_auth rpa ON r.role_id = rpa.role_id LEFT JOIN sys_permission p ON rpa.permission_id = p.permission_id <where> AND u.username = #{username} </where> </select></mapper> UserService实现类:service接口忽略。@Servicepublic class UserServiceImpl implements UserService { @Autowired private UserDao userDao; /** * 用户认证 * * @param s * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { UserPo userPo = new UserPo(); userPo.setUsername(s); userPo = userDao.getUserByLoginInfo(userPo); if (userPo == null) { return null; } return userPo; }}接下文“springboot集成Security使用token实现认证授权(二) 认证授权”......
Apr 15, 2020 10:11:45 PM
310
SPRINGBOOT之QQ登录
最近在网站上做了一个QQ登录OAuth2.0接入的功能,下面我就来分享一下如何实现QQ登录。首先在开发之前我们需要为QQ登录做如下准备:1、登录QQ互联https://connect.qq.com/成为开发者(成为开发者需要填写资料,审核时间大概2天吧)。2、有了开发者账号之后就能创建应用,这时会要求你填写网站信息,之后又会进入审核阶段(本人当时几个小时就审核过了)。接下来就是开发阶段了根据QQ互联官方网站可以知道QQ登录开发流程如下:1、获取appid和appkey,appid和appkey在你创建的应用信息中查找。2、放置QQ登录按钮。3、获取Authorization Code。4、通过Authorization Code获取Access Token。5、获取QQ用户的openid。6、通过openid访问/修改QQ用户信息。以下为开发步骤和示例代码本项目是一个maven构建的项目。使用fastjson处理json数据,fastjson依赖:<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version></dependency>一、写好配置功能1、配置基本信息## 第三方登录配置信息oauth: qq: app_id: ---------------- app_key: -------------------------------- # 获取ocde地址 get_authorization_code_uri: https://graph.qq.com/oauth2.0/authorize # 获取ocde的参数,固定为code response_type: code # 回调地址 redirect_uri: --------------------------- # 获取Access Token地址 get_access_token_uri: https://graph.qq.com/oauth2.0/token # 获取Access Token,固定为authorization_code token_grant_type: authorization_code # 刷新Access Token,refresh_token refresh_token_grant_type: refresh_token # 获取openid的地址 get_openid_uri: https://graph.qq.com/oauth2.0/me # 获取用户信息地址 get_user_info_uri: https://graph.qq.com/user/get_user_info 2、编写配置信息的配置类,通过该配置类获取配置信息@Component@ConfigurationProperties(prefix = "oauth.qq")public class QqOAuthConfig { private String appId; private String appKey; /** * 获取ocde地址 */ private String getAuthorizationCodeUri; /** * 获取ocde的参数,固定为code */ private String responseType; /** * 回调地址 */ private String redirectUri; /** * 获取Access Token地址 */ private String getAccessTokenUri; /** * 获取Access Token,固定为authorization_code */ private String tokenGrantType; /** * 刷新Access Token,refresh_token */ private String refreshTokenGrantType; /** * 获取openid的地址 */ private String getOpenidUri; /** * 获取用户信息地址 */ private String getUserInfoUri; public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getAppKey() { return appKey; } public void setAppKey(String appKey) { this.appKey = appKey; } public String getGetAuthorizationCodeUri() { return getAuthorizationCodeUri; } public void setGetAuthorizationCodeUri(String getAuthorizationCodeUri) { this.getAuthorizationCodeUri = getAuthorizationCodeUri; } public String getResponseType() { return responseType; } public void setResponseType(String responseType) { this.responseType = responseType; } public String getRedirectUri() { return redirectUri; } public void setRedirectUri(String redirectUri) { this.redirectUri = redirectUri; } public String getGetAccessTokenUri() { return getAccessTokenUri; } public void setGetAccessTokenUri(String getAccessTokenUri) { this.getAccessTokenUri = getAccessTokenUri; } public String getTokenGrantType() { return tokenGrantType; } public void setTokenGrantType(String tokenGrantType) { this.tokenGrantType = tokenGrantType; } public String getRefreshTokenGrantType() { return refreshTokenGrantType; } public void setRefreshTokenGrantType(String refreshTokenGrantType) { this.refreshTokenGrantType = refreshTokenGrantType; } public String getGetOpenidUri() { return getOpenidUri; } public void setGetOpenidUri(String getOpenidUri) { this.getOpenidUri = getOpenidUri; } public String getGetUserInfoUri() { return getUserInfoUri; } public void setGetUserInfoUri(String getUserInfoUri) { this.getUserInfoUri = getUserInfoUri; }} 二、放置QQ登录按钮三、接下来就是QQ登录主要流程。此时我们需要httpclient工具来获取通过请求返回的信息,小伙伴们可以去百度了解一下httpclient。1、加入httpclient依赖<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.7</version></dependency> 2、配置httpclient工具1)封装响应结果public class HttpClientResult implements Serializable { /** * 响应状态码 */ private int code; /** * 响应数据 */ private String content; public HttpClientResult() { } public HttpClientResult(int code) { this.code = code; } public HttpClientResult(int code, String content) { this.code = code; this.content = content; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getContent() { return content; } public void setContent(String content) { this.content = content; }} 2)封装httpClient工具类public class HttpClientUtil { // 编码格式。发送编码格式统一用UTF-8 private static final String ENCODING = "UTF-8"; // 设置连接超时时间,单位毫秒。 private static final int CONNECT_TIMEOUT = 6000; // 请求获取数据的超时时间(即响应时间),单位毫秒。 private static final int SOCKET_TIMEOUT = 6000; /** * 发送get请求;不带请求头和请求参数 * * @param url 请求地址 * @return * @throws Exception */ public static HttpClientResult doGet(String url) throws Exception { return doGet(url, null, null); } /** * 发送get请求;带请求参数 * * @param url 请求地址 * @param params 请求参数集合 * @return * @throws Exception */ public static HttpClientResult doGet(String url, Map<String, String> params) throws Exception { return doGet(url, null, params); } /** * 发送get请求;带请求头和请求参数 * * @param url 请求地址 * @param headers 请求头集合 * @param params 请求参数集合 * @return * @throws Exception */ public static HttpClientResult doGet(String url, Map<String, String> headers, Map<String, String> params) throws Exception { // 创建httpClient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); // 创建访问的地址 URIBuilder uriBuilder = new URIBuilder(url); if (params != null) { Set<Map.Entry<String, String>> entrySet = params.entrySet(); for (Map.Entry<String, String> entry : entrySet) { uriBuilder.setParameter(entry.getKey(), entry.getValue()); } } // 创建http对象 HttpGet httpGet = new HttpGet(uriBuilder.build()); /** * setConnectTimeout:设置连接超时时间,单位毫秒。 * setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection * 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。 * setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。 */ RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build(); httpGet.setConfig(requestConfig); // 设置请求头 packageHeader(headers, httpGet); // 创建httpResponse对象 CloseableHttpResponse httpResponse = null; try { // 执行请求并获得响应结果 return getHttpClientResult(httpResponse, httpClient, httpGet); } finally { // 释放资源 release(httpResponse, httpClient); } } /** * 发送post请求;不带请求头和请求参数 * * @param url 请求地址 * @return * @throws Exception */ public static HttpClientResult doPost(String url) throws Exception { return doPost(url, null, null); } /** * 发送post请求;带请求参数 * * @param url 请求地址 * @param params 参数集合 * @return * @throws Exception */ public static HttpClientResult doPost(String url, Map<String, String> params) throws Exception { return doPost(url, null, params); } /** * 发送post请求;带请求头和请求参数 * * @param url 请求地址 * @param headers 请求头集合 * @param params 请求参数集合 * @return * @throws Exception */ public static HttpClientResult doPost(String url, Map<String, String> headers, Map<String, String> params) throws Exception { // 创建httpClient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); // 创建http对象 HttpPost httpPost = new HttpPost(url); /** * setConnectTimeout:设置连接超时时间,单位毫秒。 * setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection * 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。 * setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。 */ RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build(); httpPost.setConfig(requestConfig); // 设置请求头 /*httpPost.setHeader("Cookie", ""); httpPost.setHeader("Connection", "keep-alive"); httpPost.setHeader("Accept", "application/json"); httpPost.setHeader("Accept-Language", "zh-CN,zh;q=0.9"); httpPost.setHeader("Accept-Encoding", "gzip, deflate, br"); httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");*/ packageHeader(headers, httpPost); // 封装请求参数 packageParam(params, httpPost); // 创建httpResponse对象 CloseableHttpResponse httpResponse = null; try { // 执行请求并获得响应结果 return getHttpClientResult(httpResponse, httpClient, httpPost); } finally { // 释放资源 release(httpResponse, httpClient); } } /** * 发送put请求;不带请求参数 * * @param url 请求地址 * @param * @return * @throws Exception */ public static HttpClientResult doPut(String url) throws Exception { return doPut(url); } /** * 发送put请求;带请求参数 * * @param url 请求地址 * @param params 参数集合 * @return * @throws Exception */ public static HttpClientResult doPut(String url, Map<String, String> params) throws Exception { CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPut httpPut = new HttpPut(url); RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build(); httpPut.setConfig(requestConfig); packageParam(params, httpPut); CloseableHttpResponse httpResponse = null; try { return getHttpClientResult(httpResponse, httpClient, httpPut); } finally { release(httpResponse, httpClient); } } /** * 发送delete请求;不带请求参数 * * @param url 请求地址 * @param * @return * @throws Exception */ public static HttpClientResult doDelete(String url) throws Exception { CloseableHttpClient httpClient = HttpClients.createDefault(); HttpDelete httpDelete = new HttpDelete(url); RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build(); httpDelete.setConfig(requestConfig); CloseableHttpResponse httpResponse = null; try { return getHttpClientResult(httpResponse, httpClient, httpDelete); } finally { release(httpResponse, httpClient); } } /** * 发送delete请求;带请求参数 * * @param url 请求地址 * @param params 参数集合 * @return * @throws Exception */ public static HttpClientResult doDelete(String url, Map<String, String> params) throws Exception { if (params == null) { params = new HashMap<String, String>(); } params.put("_method", "delete"); return doPost(url, params); } /** * Description: 封装请求头 * * @param params * @param httpMethod */ public static void packageHeader(Map<String, String> params, HttpRequestBase httpMethod) { // 封装请求头 if (params != null) { Set<Map.Entry<String, String>> entrySet = params.entrySet(); for (Map.Entry<String, String> entry : entrySet) { // 设置到请求头到HttpRequestBase对象中 httpMethod.setHeader(entry.getKey(), entry.getValue()); } } } /** * Description: 封装请求参数 * * @param params * @param httpMethod * @throws UnsupportedEncodingException */ public static void packageParam(Map<String, String> params, HttpEntityEnclosingRequestBase httpMethod) throws UnsupportedEncodingException { // 封装请求参数 if (params != null) { List<NameValuePair> nvps = new ArrayList<NameValuePair>(); Set<Map.Entry<String, String>> entrySet = params.entrySet(); for (Map.Entry<String, String> entry : entrySet) { nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } // 设置到请求的http对象中 httpMethod.setEntity(new UrlEncodedFormEntity(nvps, ENCODING)); } } /** * Description: 获得响应结果 * * @param httpResponse * @param httpClient * @param httpMethod * @return * @throws Exception */ public static HttpClientResult getHttpClientResult(CloseableHttpResponse httpResponse,CloseableHttpClient httpClient, HttpRequestBase httpMethod) throws Exception { // 执行请求 httpResponse = httpClient.execute(httpMethod); // 获取返回结果 if (httpResponse != null && httpResponse.getStatusLine() != null) { String content = ""; if (httpResponse.getEntity() != null) { content = EntityUtils.toString(httpResponse.getEntity(), ENCODING); } return new HttpClientResult(httpResponse.getStatusLine().getStatusCode(), content); } return new HttpClientResult(HttpStatus.SC_INTERNAL_SERVER_ERROR); } /** * Description: 释放资源 * * @param httpResponse * @param httpClient * @throws IOException */ public static void release(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient) throws IOException { // 释放资源 if (httpResponse != null) { httpResponse.close(); } if (httpClient != null) { httpClient.close(); } }} 3、点击QQ登录的按钮获取会跳转到QQ登录页面。如果QQ授权登录成功将会跳转到指定的回调地址(创建应用是填写的地址),获取到Authorization Code,该code有效期10分钟。@GetMapping("/login/qq")public void loginQQ(HttpServletResponse httpServletResponse, String thirdLoginBackHref) { try { httpServletResponse.sendRedirect(qqOAuthConfig.getGetAuthorizationCodeUri() + "?response_type=" + qqOAuthConfig.getResponseType() + "&client_id=" + qqOAuthConfig.getAppId() + "&redirect_uri=" + qqOAuthConfig.getRedirectUri() + "&state=" + UuidUtil.getUuid()); } catch (Exception e) { throw new BaseException("请求参数异常"); }} 1、编写回调地址。通过Authorization Code获取Access Token(2小时有效期);获取QQ用户的openid;通过openid访问QQ用户信息。1)回调controller@GetMapping("/login/qq/callback")public String qqCallbackUri(String code) { loginService.qqLogin(code); return "/"; //返回地址} 2)回调service,获取用户信息。@Override@Transactionalpublic void qqLogin(String code) { //获取access_token的请求参数 HashMap<String, String> getTokenParams = new HashMap<>(); getTokenParams.put("grant_type", qqOAuthConfig.getTokenGrantType()); getTokenParams.put("client_id", qqOAuthConfig.getAppId()); getTokenParams.put("client_secret", qqOAuthConfig.getAppKey()); getTokenParams.put("code", code); getTokenParams.put("redirect_uri", qqOAuthConfig.getRedirectUri()); //获取access_token HttpClientResult resultOfGetAcccessToken = null; try { resultOfGetAcccessToken = HttpClientUtil.doGet(qqOAuthConfig.getGetAccessTokenUri(), getTokenParams); } catch (Exception e) { throw new BaseException("QQ登录异常"); } //获取access_token返回参数 String resultParamsOfGetToken = resultOfGetAcccessToken.getContent(); if (resultOfGetAcccessToken.getCode() != 200 || (resultParamsOfGetToken == null || resultParamsOfGetToken.equals(""))) { throw new BaseException("QQ登录异常"); } //处理获取access_token返回参数成json String[] tokenResultString = resultParamsOfGetToken.split("&"); Map<String, String> tokenResultMap = new HashMap<>(); for (String s : tokenResultString) { String keyValue[] = s.split("="); if (keyValue.length > 1) { tokenResultMap.put(keyValue[0], keyValue[1]); } } //access_token String accessToken = tokenResultMap.get("access_token"); //获取openid HttpClientResult resultOfGetOpenid = null; try { resultOfGetOpenid = HttpClientUtil.doGet(qqOAuthConfig.getGetOpenidUri() + "?access_token=" + accessToken); } catch (Exception e) { throw new BaseException("QQ登录异常"); } //处理获取openid结果 String resultOfOpenidContent = resultOfGetOpenid.getContent(); if (resultOfGetOpenid.getCode() != 200 || (resultOfOpenidContent == null || resultOfOpenidContent.equals(""))) { throw new BaseException("QQ登录异常"); } JSONObject resultByGetOpenIdJson = JSON.parseObject(resultOfOpenidContent.replaceAll("callback\\( | \\);", "").trim()); //openid String openid = (String) resultByGetOpenIdJson.get("openid"); if (resultByGetOpenIdJson.get("client_id") == null || openid == null) { throw new BaseException("QQ登录异常"); } //获取用户信息 HttpClientResult resultByGetUserInfo = null; try { resultByGetUserInfo = HttpClientUtil.doGet(qqOAuthConfig.getGetUserInfoUri() + "?access_token=" + accessToken + "&oauth_consumer_key=" + qqOAuthConfig.getAppId() + "&openid=" + openid); } catch (Exception e) { throw new BaseException("QQ登录异常"); } //处理用户数据 JSONObject qqUserInfoJson = JSON.parseObject(resultByGetUserInfo.getContent()); //qq头像 String chatHead = (String) qqUserInfoJson.get("figureurl_qq_1"); //qq昵称 String nickname = (String) qqUserInfoJson.get("nickname"); //以下便是登录逻辑,这里就省略不多写了,大家可以根据自己的需求编写登录逻辑。} 说明:上面代码最后的变量qqUserInfoJson便是一个包含了QQ用户信息的json对象,我们可以将自己需要的用户信息取出来,必要时保存在数据库。 虽然省略的登录部分的代码,但在这里也简要说一下QQ登录逻辑:首先QQ登录对于自己的项目肯定是免密登录;其次openid是每个QQ用户在本网站的唯一标识,所以我们QQ登录的用户表一般会存每个用户的openid,在获取到QQ用户信息时判断数据库是否存在与当前openid相同的用户数据,如果没有便添加一条与当前openid相同的用户信息然后登录,如果存在,可以选择直接进入登录逻辑或修改相关登录信息后进入登录逻辑。 这里只介绍简单的QQ登录流程,并没有持续去请求腾讯API获取相关信息,所以刷新access_token功能以后再加入。 至此QQ登录就分享完了,感谢大家的支持!
Mar 20, 2019 9:40:40 PM
808