Я слышал, что поиск в WeChat для «Java Fish Aberdeen» станет сильнее!
Эта статья включена вJavaStarter, который содержит мою полную серию статей по Java, вы можете прочитать его для изучения или интервью.
(Введение
Когда я говорил о распределенных сессиях в предыдущей статье, некоторые читатели спрашивали, не хорошо ли использовать JWT, даже сервер Redis сохраняется, и может быть реализована аутентификация информации о персонале в распределенной среде. Так уж получилось, что я еще не написал статью о JWT, а просто использовал эту технологию в своем проекте, поэтому сегодня поделюсь. Все то же самое, сначала теория, а потом практика, и мозговой штурм вместе с комментариями и комментариями.
(2) Что такое JWT
JWT весь процессJSON Web TokenИнформация о сеансе пользователя хранится в браузере клиента, между сторонами эта информация может быть зашифрована с помощью алгоритма шифрования объекта JSON, обеспечивающего безопасную передачу информации.
Это описание может быть трудным для понимания, если говорить простыми словами,JWT должен генерировать строку токенов зашифрованной строки при входе в систему и передавать эту строку токенов с каждым запросом., если сервер может расшифровать эту строку, это означает, что он находится в состоянии входа в систему.
Обычно мы шифруем неконфиденциальные данные для создания токенов, и если серверу необходимо использовать информацию о персонале, расшифровываем информацию о персонале.
Вы обнаружите, что использование JWT напрямую решает проблему аутентификации персонала в среде распределенного кластера, поскольку сгенерированная строка токенов может быть проанализирована на любом сервере. Как показано ниже:
Разобравшись с принципом, поговорим о самом факте, данные JWT состоят из трех частей:
1. Заголовок 2, полезная нагрузка 3, подпись
Эти три объединяются знаком «.», и после шифрования генерируется токен:
Header.Payload.Signature
Ниже приведен токен, который я сгенерировал
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZheXoiLCJjcmVhdGVkYXRlIjoxNjExNzU1MDIxNjk1LCJpZCI6MSwiZXhwIjoxNjEyMzU5ODIxLCJ1c2VyTGV2ZWxJZCI6bnVsbH0.ahFWQ_BJ1WNWp9GnlTrSNThVa3i3dydzcaNxLmPb7HI
Заголовок используется для описания метаданных JWT, включая два данных, где alg представляет алгоритм подписи, значение по умолчанию — HS256, а атрибут typ представляет тип токена, здесь — JWT. Данные перед первым «.» в сгенерированном выше токене представляют собой следующий json после декодирования base64.
{
alg : "HS256",
typ : "JWT"
}
Полезная нагрузка используется для записи информации о пользователе в формате JSON. Информация о пользователе здесь может быть настроена. Данные между первым «.» и вторым «.» выше декодируются с помощью base64 и представляют собой следующий json
{
"sub":"javayz",
"createdate":1611755021695,
"id":1,
"exp":1612359821,
"userLevelId":null
}
Подпись хранит зашифрованную строку, которая связана с указанным алгоритмом.Шифрование заголовка и полезной нагрузки, каждая часть отделяется знаком «.». составляет жетон.
(3) Процесс аутентификации личности JWT
После прочтения вышеприведенного содержания я считаю, что вы уже знаете аутентификацию JWT, Процесс аутентификации выглядит следующим образом:
1. Пользователь вводит имя пользователя и пароль для входа
2. Сервер проверяет правильность имени пользователя и пароля и, если они верны, возвращает токен клиенту.
3. Клиент сохраняет токен в файле cookie или локальном хранилище.
4. Каждый последующий запрос клиента должен приносить этот токен
5. Сервер использует токен, чтобы определить, какой это пользователь.
(4) Практика кодекса
Затем реализация аутентификации JWT моделируется кодом.Среда, когда я пишу этот код, — springBoot2.4.2, и вводятся зависимости JWT:
<!--JWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
Настройте некоторые свойства в файле конфигурации, которые будут использоваться далее:
jwt:
tokenHeader: Authorization
secret: javayz
expiration: 604800
tokenHead: Bearer
Напишите класс для чтения этих параметров:
@Data
@ConfigurationProperties(prefix = "jwt")
@Component
public class JwtProperties {
private String tokenHeader;
private String secret;
private Long expiration;
private String tokenHead;
}
Напишите класс инструмента JWT для реализации функций генерации и декодирования токенов:
public class JwtKit {
@Autowired
private JwtProperties jwtProperties;
/**
* 创建token
* @param user
* @return
*/
public String generateJwtToken(User user){
//所有的用户数据放在claims中
Map<String,Object> claims=new HashMap<>();
claims.put("sub",user.getUsername());
claims.put("createdate",new Date());
claims.put("id",user.getId());
claims.put("userLevelId",user.getLevelId());
return Jwts.builder()
.setClaims(claims)
.setHeaderParam("typ", "JWT")
.setExpiration(new Date(System.currentTimeMillis()+jwtProperties.getExpiration()*1000))
.signWith(SignatureAlgorithm.HS256, jwtProperties.getSecret())
.compact();
}
/**
* 解码token
* @param jwtToken
* @return
*/
public Claims parseJwtToken(String jwtToken){
Claims claims=null;
try {
claims=Jwts.parser()
.setSigningKey(jwtProperties.getSecret())
.parseClaimsJws(jwtToken)
.getBody();
}catch (ExpiredJwtException e){
throw new MyException("JWTtoken过期");
}catch (UnsupportedJwtException e){
throw new MyException("JWTtoken格式不支持");
}catch (MalformedJwtException e){
throw new MyException("无效的token");
}catch (SignatureException e){
throw new MyException("无效的token");
}catch (IllegalArgumentException e){
throw new MyException("无效的token");
}
return claims;
}
}
Затем инструменты высыпают в контейнер Bean:
@Configuration
public class JwtConfiguration {
@Bean
public JwtKit jwtKit(){
return new JwtKit();
}
}
Тогда вы можете использовать его с удовольствием
@RestController
@RequestMapping("/sso")
public class UserController extends BaseController {
@Autowired
private UserService userService;
@Autowired
private JwtKit jwtKit;
@Autowired
private JwtProperties jwtProperties;
@PostMapping("jwtlogin")
public CommonResult jwtLogin(@RequestParam String username,@RequestParam String password){
User user = userService.login(username, password);
if (user!=null){
Map<String,Object> map=new HashMap<>();
String token=jwtKit.generateJwtToken(user);
map.put("tokenHead",jwtProperties.getTokenHead());
map.put("token",token);
return new CommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),map);
}
return new CommonResult(ResponseCode.USER_NOT_EXISTS.getCode(),ResponseCode.USER_NOT_EXISTS.getMsg(),"");
}
}
Если вход выполнен успешно, начало токена и фактическое значение токена будут возвращены серверной части.
Затем напишите перехватчик для перехвата других запросов, кроме /sso/*, к которым можно получить доступ только войдя в систему.
@Configuration
public class IntercepterConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
List list=new ArrayList();
list.add("/sso/**");
registry.addInterceptor(authInterceptorHandler())
.addPathPatterns("/**")
.excludePathPatterns(list);
}
@Bean
public AuthInterceptorHandler authInterceptorHandler(){
return new AuthInterceptorHandler();
}
}
Дальше идет обработка перехвата, сначала судитьВ шапке есть данные с ключом Авторизация?, если нет, то это означает, что вы не вошли в систему, и будет возвращен результат не вошел в систему. Если в шапке есть данные с ключом Авторизация,Затем сначала определите, начинается ли он с Bearer(Это можно настроить), если это так, то это означает, что вы вошли в систему, и возвращаете true напрямую, не перехватывая.
@Slf4j
public class AuthInterceptorHandler implements HandlerInterceptor {
@Autowired
private JwtKit jwtKit;
@Autowired
private JwtProperties jwtProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("进入拦截器");
String aa = request.getHeader("aa");
String authorization =request.getHeader(jwtProperties.getTokenHeader());
log.info("Authorization"+authorization);
//如果不为空,说明head里存了数据,
if(StringUtils.isNotEmpty(authorization) && authorization.startsWith(jwtProperties.getTokenHead())){
String authToken = authorization.substring(jwtProperties.getTokenHead().length());
Claims claims=null;
try {
claims=jwtKit.parseJwtToken(authToken);
if (claims!=null){
return true;
}
}catch (MyException e){
log.info(e.getMessage()+":"+authToken);
}
}
response.setHeader("Content-Type","application/json");
response.setCharacterEncoding("UTF-8");
String result = new ObjectMapper().writeValueAsString(new CommonResult(ResponseCode.NEED_LOGIN.getCode(), ResponseCode.NEED_LOGIN.getMsg(), ""));
response.getWriter().println(result);
return false;
}
}
Наконец, напишите тестовый класс:
@RestController
public class IndexController extends BaseController{
@Autowired
private JwtProperties jwtProperties;
@Autowired
private JwtKit jwtKit;
@RequestMapping(value = "/index",method = RequestMethod.GET)
public CommonResult index(){
String authorization = getRequest().getHeader(jwtProperties.getTokenHeader());
String authToken = authorization.substring(jwtProperties.getTokenHead().length());
Claims claims=jwtKit.parseJwtToken(authToken);
return new CommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),claims.get("sub"));
}
}
(5) Тест на эффект
Сначала я иду прямо кhttp://localhost:8189/index, возвращается результат, что логин недействителен, так как заголовок не передается
Итак, сначала войдите в интерфейс входа:http://localhost:8189/sso/jwtlogin
Конкретный токенхед возвращаемый токен и фактические значения. Этот токен в заголовок, снова интерфейсы доступа к индексам:
Операция выполнена успешно, и можно получить информацию о пользователе.
(6) Сравнение JWT и сеанса
В предыдущей статье я использовал Session для реализации функции аутентификации, но для реализации распределенной аутентификации требуется дополнительный сервер Redis. Использование JWT не требует дополнительного сервера, он помещает токен в заголовок.
Но есть у JWT и недостатки, самый очевидный - вот этот абзацТокен не может быть аннулирован вручнуюПосле того, как токен генерируется, даже если вы выходите из системы, и войдите в систему, токен все еще действителен.
Вторым недостатком является то, что информация представляет собой данные после Base64, что эквивалентно использованию до тех пор, пока вы ее получаете.JWT может передавать только неконфиденциальные данные персонала.
Третий недостаток заключается в том, что каждый запрос должен содержать информацию о токене в заголовке.повышенное давление полосы пропускания. Не думайте, что заголовок запроса занимает немного больше пропускной способности, а если это 10 000 или 100 000 запросов? Ресурс пропускной способности очень ценен.
Таким образом, преимущества и недостатки JWT и сеанса, как использовать, зависит от ситуации в вашей системе. Ну в чем проблема Комментарии Гостевая книга, мы очередные до свидания!