Spring Security实现用户认证四:使用JWT与Redis实现无状态认证
- 1 什么是无状态认证?
- 2 什么是JWT?
- 2.1 需要注意的事项
- 2.2 JWT构成
- 3 Spring Security + JWT实现无状态认证
- 3.1 创建一个Spring Boot项目
- 3.1.1 依赖
- 3.1.2 Main
- 3.1.3 application.yml
- 3.2 Controller
- 3.2.1 LoginController
- 3.3.2 IndexController
- 3.3 User Entity
- 3.4 Service
- UserService
- UserServiceImpl
- 3.5 UserMapper.java
- 3.6 UserMapper.xml
- 3.7 JwtTokenProvider.java
- 3.8 Redis配置类
- 3.9 MyRedisSecurityContextRepository.java
- 3.10 DBUserDetailManager.java
- 3.11 SpringSecurityConfig配置类
- 3.12 MyAuthenticationEntryPoint
1 什么是无状态认证?
在基本的通信流程中,我们一般采用Session去存储用户的认证状态。在Spring Security实现用户认证三中讲过,在拿到前端传输过来的用户名和密码之后,会有专门的过滤器UsernamePasswordAuthenticationFilter
处理这部分的需求,并且对认证成功的用户生成Token且存储在Session中。在下次发起请求时,直接从Session中取出同用户名的token进行密码哈希的比较要认证用户。
对于无状态认证,则我们的认证不依赖与服务器端存储的Session的状态。所以无状态认证需要我们每次从前端传输一个包含完整认证信息的Token到服务器端进行自定义的认证过程,这使得服务器无需存储和管理会话数据。常见的无状态认证方法包括 JSON Web Token (JWT)、API Key和 OAuth 2.0。
2 什么是JWT?
JWT(JSON Web Token)是一种基于JSON的开放标准(RFC 7519),用于在各方之间传递信息。JWT可以进行数字签名,并且可以选择加密其内容。它定义了一种紧凑和自包含的方式, 可以通过URL、POST参数或HTTP头在各方之间安全地传输信息。此信息可以进行验证和信任,因为它是经过数字签名的,但是签名不能保证数据的机密性。JWT 可以使用 HMAC 算法、RSA 或 ECDSA 的公钥/私钥对进行签名。
JWT最常见的用途是用户身份验证。一旦用户登录成功,服务器会生成一个JWT并返回给客户端。客户端将JWT存储在本地(如localStorage或cookie),并在每次请求时将其发送到服务器,服务器通过验证JWT来验证用户身份。
2.1 需要注意的事项
- 保密:不要在JWT中存储敏感信息,因为JWT是可以被解码的。
- 过期处理:设置合理的过期时间,并且在需要时支持刷新令牌机制。
- 使用HTTPS:确保在传输JWT时使用HTTPS,防止中间人攻击。
2.2 JWT构成
JWT由三个主要部分构成:Header(头部)、Payload(负载)和 Signature(签名)。每个部分都有其特定的作用和结构。
- Header(头部)
头部通常包含两个部分:令牌类型和使用的签名算法。头部数据结构为一个JSON对象,然后进行Base64Url编码。
{"alg": "HS256","typ": "JWT"
}
- Payload(负载)
负载部分包含了声明(claims),即需要传输的数据。这些数据可以是关于用户的信息或者其他的元数据。声明可以分为三类:
- Registered claims(注册声明):预定义的一些声明,如 iss(签发者),exp(过期时间),sub(主题),aud(受众)。
- Public claims(公共声明):可以自定义的声明,但为了避免冲突,应使用URI命名。
- Private claims(私有声明):由双方约定的声明,用于信息交换。
{"sub": "1234567890","name": "John Doe","admin": true
}
- Signature(签名)
签名部分用于验证消息的发送者和确保消息在传递过程中未被篡改。签名的生成过程如下:
- 将编码后的Header和Payload用句点 (
.
) 连接起来:
base64UrlEncode(header) + "." + base64UrlEncode(payload)
- 使用头部中指定的签名算法,并结合一个密钥对上述连接的字符串进行签名。
- 对于HMAC SHA256算法,签名过程如下:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret
)
最终,JWT的格式为:
header.payload.signature
3 Spring Security + JWT实现无状态认证
登录认证流程如上。
3.1 创建一个Spring Boot项目
3.1.1 依赖
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.12.3</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.12.3</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- 或者jjwt-gson,如果你更喜欢Gson --><version>0.12.3</version><scope>runtime</scope>
</dependency><!-- redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency><!-- 数据库 -->
<dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId>
</dependency>
<dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId>
</dependency>
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.21</version>
</dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId>
</dependency>
3.1.2 Main
package com.song.cloud;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;@SpringBootApplication
@MapperScan("com.song.cloud.mapper")
public class ServiceSecurityJwt6501 {public static void main(String[] args) {SpringApplication.run(ServiceSecurityJwt6501.class, args);}
}
3.1.3 application.yml
spring:application:name: service-security-jwtdatasource:driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourceusername: rootpassword: root# 注意修改数据库名字url: jdbc:mysql://localhost:3306/test? characterEncoding=utf8&useSSL=false&serverTimeZone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=truedata: # 配置redisredis:port: 6379host: 192.168.62.128password: 1234app:jwt-sign-secret: gOk33w29WESOMEx8vUQLb69AsGhlUb7UmrFwu3g2TOo=jwt-expiration-milliseconds: 604800000 # 七天过期server:port: 6501logging:level:web: debugorg.springframework.security: debugmybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.song.cloud.entities # 注意修改成自己的包名configuration:map-underscore-to-camel-case: true
3.2 Controller
3.2.1 LoginController
用来处理
package com.song.cloud.controller;import com.song.cloud.entities.User;
import com.song.cloud.service.UserService;
import com.song.cloud.utils.JwtTokenProvider;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;@RestController
public class LoginController {@Resourceprivate JwtTokenProvider jwtTokenProvider;@Resourceprivate UserService userService;@Resourceprivate RedisTemplate<String, Object> redisTemplate;@PostMapping("/api/auth")public String auth(@RequestBody User user){System.out.println(user);UserDetails userDetails = userService.loadUserDetail(user);PasswordEncoder delegatingPasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();boolean matches = delegatingPasswordEncoder.matches(user.getPasswordHash(), userDetails.getPassword());if(matches){UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());token.setDetails(userDetails);System.out.println(token);//保存token到redisredisTemplate.opsForValue().set(userDetails.getUsername(), token);return jwtTokenProvider.generateToken(token);}return "fail";}
}
3.3.2 IndexController
package com.song.cloud.controller;import jakarta.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Collection;
import java.util.HashMap;
import java.util.Map;@RestController
public class IndexController {@GetMapping("/")public Map index() {SecurityContext context = SecurityContextHolder.getContext();Authentication authentication = context.getAuthentication();Object principal = authentication.getPrincipal();Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); //脱敏处理Object credentials = authentication.getCredentials();HashMap<Object, Object> map = new HashMap<>();map.put("username", authentication.getName());map.put("authorities", authorities);map.put("credentials", credentials);map.put("details", authentication.getDetails());map.put("principal", principal);return map;}
}
3.3 User Entity
package com.song.cloud.entities;import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;/*** 表名:t_users_test
*/
@Table(name = "t_users_test")
public class User {/*** id*/@Id@GeneratedValue(generator = "JDBC")private Long id;/*** 用户名*/private String username;/*** 密码hash*/@Column(name = "password_hash")private String passwordHash;/*** 是否启用*/private Boolean enable;/*** 获取id** @return id - id*/public Long getId() {return id;}/*** 设置id** @param id id*/public void setId(Long id) {this.id = id;}/*** 获取用户名** @return username - 用户名*/public String getUsername() {return username;}/*** 设置用户名** @param username 用户名*/public void setUsername(String username) {this.username = username;}/*** 获取密码hash** @return passwordHash - 密码hash*/public String getPasswordHash() {return passwordHash;}/*** 设置密码hash** @param passwordHash 密码hash*/public void setPasswordHash(String passwordHash) {this.passwordHash = passwordHash;}/*** 获取是否启用** @return enable - 是否启用*/public Boolean getEnable() {return enable;}/*** 设置是否启用** @param enable 是否启用*/public void setEnable(Boolean enable) {this.enable = enable;}@Overridepublic String toString() {return "User{" +"enable=" + enable +", id=" + id +", username='" + username + '\'' +", passwordHash='" + passwordHash + '\'' +'}';}
}
3.4 Service
UserService
package com.song.cloud.service;import com.song.cloud.entities.User;
import org.springframework.security.core.userdetails.UserDetails;import java.util.List;public interface UserService {UserDetails loadUserDetail(User user);
}
UserServiceImpl
package com.song.cloud.service.impl;import com.song.cloud.config.DBUserDetailManager;
import com.song.cloud.entities.User;
import com.song.cloud.mapper.UserMapper;
import com.song.cloud.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserServiceImpl implements UserService {@Resourceprivate DBUserDetailManager dbUserDetailsManager;@Resourceprivate UserMapper userMapper;@Overridepublic List<User> list() {return userMapper.selectAll();}@Overridepublic UserDetails loadUserDetail(User user) {return dbUserDetailsManager.loadUserByUsername(user.getUsername());}}
3.5 UserMapper.java
package com.song.cloud.mapper;import com.song.cloud.entities.User;
import tk.mybatis.mapper.common.Mapper;public interface UserMapper extends Mapper<User> {
}
3.6 UserMapper.xml
注意将相应信息改成自己的。包路径、实体名、mapper类名等。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.song.cloud.mapper.UserMapper"><resultMap id="BaseResultMap" type="com.song.cloud.entities.User"><!--WARNING - @mbg.generated--><id column="id" jdbcType="BIGINT" property="id" /><result column="username" jdbcType="VARCHAR" property="username" /><result column="password_hash" jdbcType="VARCHAR" property="passwordHash" /><result column="enable" jdbcType="BIT" property="enable" /></resultMap>
</mapper>
3.7 JwtTokenProvider.java
主要用来创建JwtToken的。
package com.song.cloud.utils;import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
import java.util.Date;@Component
public class JwtTokenProvider {private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);@Value("${app.jwt-sign-secret}")private String jwtSignSecret;@Value("${app.jwt-expiration-milliseconds}")private long jwtExpirationDate;// 生成 JWT tokenpublic String generateToken(Authentication authentication) {// 构建一个JWT,它的注册声明(Subject)设为 usernameString username = authentication.getName();Date currentDate = new Date();Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate);// 使用适合HMAC-SHA-256算法的密钥对JWT进行签名。// 将其紧凑压缩为最终的字符串形式。签名后的JWT称为'token'。String token = Jwts.builder().issuer("backend").subject(username).issuedAt(new Date()).expiration(expireDate).signWith(key()).compact();return token;}private SecretKey key() {return Keys.hmacShaKeyFor(//从base64解码得到byte[]Decoders.BASE64.decode(jwtSignSecret));}// 从 Jwt token 获取用户名public String getUsername(String token) {Claims claims = Jwts.parser().verifyWith(key()).build().parseSignedClaims(token).getPayload();return claims.getSubject();}// 验证 Jwt tokenpublic boolean validateToken(String token) {try {Jwts.parser().verifyWith(key()).build().parse(token);return true;} catch (MalformedJwtException e) {logger.error("Invalid JWT token: {}", e.getMessage());} catch (ExpiredJwtException e) {logger.error("JWT token is expired: {}", e.getMessage());} catch (UnsupportedJwtException e) {logger.error("JWT token is unsupported: {}", e.getMessage());} catch (IllegalArgumentException e) {logger.error("JWT claims string is empty: {}", e.getMessage());} catch (SignatureException e){logger.error("JWT signature validation fails: {}", e.getMessage());}return false;}
}
3.8 Redis配置类
package com.song.cloud.config;import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.jackson2.SecurityJackson2Modules;import java.util.*;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new JdkSerializationRedisSerializer());return template;}}
3.9 MyRedisSecurityContextRepository.java
实现了SecurityContextRepository
,使用redis存储和管理SecurityContext
。
package com.song.cloud.config;import com.song.cloud.utils.JwtTokenProvider;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;@Component
public class MyRedisSecurityContextRepository implements SecurityContextRepository {@Resourceprivate JwtTokenProvider jwtTokenProvider;@Resourceprivate RedisTemplate<String, Object> redisTemplate;private final SecurityContextHolderStrategy contextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();@Overridepublic SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {SecurityContext emptyContext = this.contextHolderStrategy.createEmptyContext();HttpServletRequest request = requestResponseHolder.getRequest();String token = request.getHeader("Authorization");if(!StringUtils.hasText(token)) return emptyContext;String username = jwtTokenProvider.getUsername(token);if(username == null) return emptyContext;UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken)redisTemplate.opsForValue().get(username);emptyContext.setAuthentication(usernamePasswordAuthenticationToken);return emptyContext;}@Overridepublic void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {System.out.println("saveContext-----------------------------------------");}@Overridepublic boolean containsContext(HttpServletRequest request) {System.out.println("containsContext------------------------------------");CharSequence authorization = request.getHeader("Authorization");return StringUtils.hasText(authorization);}
}
3.10 DBUserDetailManager.java
用于实现数据库认证
package com.song.cloud.config;import com.song.cloud.entities.User;
import com.song.cloud.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Component;
import tk.mybatis.mapper.entity.Example;import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;@Component
public class DBUserDetailManager implements UserDetailsManager, UserDetailsPasswordService, Serializable {@Resourceprivate UserMapper userMapper;private Collection<GrantedAuthority> authorities;private DBUserDetailManager(ArrayList<GrantedAuthority> authorities){this.authorities = authorities;}@Overridepublic UserDetails updatePassword(UserDetails user, String newPassword) {return null;}@Overridepublic void createUser(UserDetails userDetails) {}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic boolean userExists(String username) {return false;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {System.out.println("进入:DBUserDetailManager ");//查询数据库根据用户名Example example = new Example(User.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("username", username);User user = userMapper.selectOneByExample(example);authorities.add(() -> "rule");if (user == null) {throw new UsernameNotFoundException(username);}return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPasswordHash(),true,true,true,true,authorities);}
}
3.11 SpringSecurityConfig配置类
配置Spring Security的配置类
package com.song.cloud.config;import com.song.cloud.handler.JwtAuthenticationEntryPoint;
import com.song.cloud.handler.MyAuthenticationEntryPoint;
import com.song.cloud.handler.MyAuthenticationSuccessHandler;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;@Configuration
@EnableWebSecurity
public class SpringSecurityConfig {@Resourceprivate MyRedisSecurityContextRepository myRedisSecurityContextRepository;@Beanpublic static PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) -> {authorize.requestMatchers("/api/auth/**").permitAll();authorize.anyRequest().authenticated();}).formLogin(Customizer.withDefaults());http.sessionManagement(session->{session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);});http.securityContext(context->{context.securityContextRepository(myRedisSecurityContextRepository);});http.exceptionHandling(exception -> {exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());});http.csrf(AbstractHttpConfigurer::disable);http.cors(Customizer.withDefaults());return http.build();}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {return configuration.getAuthenticationManager();}
}
3.12 MyAuthenticationEntryPoint
处理认证失败的请求。
package com.song.cloud.handler;import cn.hutool.json.JSONUtil;
import com.song.cloud.resp.ResultData;
import com.song.cloud.resp.ReturnCodeEnum;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;import java.io.IOException;public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {System.out.println("进入:MyAuthenticationEntryPoint");String localizedMessage = authException.getLocalizedMessage();ResultData<Object> fail = ResultData.fail(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), localizedMessage);String jsonStr = JSONUtil.toJsonStr(fail);response.setContentType("application/json;charset=UTF-8");response.getWriter().print(jsonStr);}
}