Оригинал: Ape Logic, добро пожаловать, пожалуйста, сохраните источник для перепечатки.
Полный пример кода этой статьи см. в репозитории github. Xiaoq вводит в текст только самые важные блоки кода.
https://github.com/yuanluoji/purestart-springboot-jwt
Что касается jwt, я не буду здесь вдаваться в подробности. В общем, имеетHeader
,Payload
,Signature
Каждая из трех частей имеет некоторые подразделенные атрибуты.На этот принцип можно взглянуть, и он не имеет существенной помощи для нашего использования.
использоватьjwt
Это может сделать фоновую службу полностью безгражданной, так что процесс входа в систему не требует участия сеанса, так что сервер будет иметь сильные возможности горизонтального расширения.nginx
настройка не требуетсяip_hash
Такая боль в заднице.
Я обнаружил, что многие примеры кода jwt очень сложны и расплывчаты. Особенно при интеграции со SpringBoot этот процесс усложняется из-за участия SpringSecurity.
В этой статье в основном будет представлена интеграция со SpringBoot, чтобы jwt действительно можно было применить на практике.
Во-первых, давайте посмотрим, как выглядит пальто jwt. Это длинный список ниже.
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ5dWFubHVvamkiLCJpYXQiOjE2MDI0OTA0NDEsImV4cCI6MTYwMjQ5MjI0MX0.Qrz30s56F--cQu_fs0LWQhiZtcoLbdAuQK6dIVk4b_aSZ5is8nTs1bR7mh0qefZdiFvFk4N__sg0UouKbhH8_g
Чувствительные одноклассники могут видеть с первого взгляда, последний шаг - пройтиbase64
закодировано. Используя официальную html-страницу для расшифровки, вы можете увидеть, что это просто кодировка, а содержимое не зашифровано. Мы можем легко получить из Playloadyuanluoji
такие слова.
1. Использование JWT
С JWT мы ожидаем получить функцию проверки входа, использовать ее для замены файлов cookie и использовать ее для имитации сеанса. Общий процесс использования выглядит следующим образом:
- Внешний интерфейс отправляет имя пользователя и пароль на любой сервер
- Сервер аутентифицирует имя пользователя и пароль (
spring security
илиshiro
) - Если проверка прошла успешно, токен будет сгенерирован с помощью API jwt.
- Этот токен будет возвращен интерфейсу, и интерфейс сохранит его (куки, контекст или другое), а затем каждый запрос будет добавлять этот токен в заголовок http и отправлять его на сервер
- Сервер может проверить легитимность токена, потому что есть срок действия и механизм защиты от несанкционированного доступа, поэтому токен нужно отправлять полностью
В Java есть два наиболее популярных пакета. Один из них — официальный auth0, но, похоже, он используется меньше, потому что он сложнее; другой — jjwt. Его можно импортировать напрямую через файл pom.
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
Давайте сначала посмотрим, как используется Jwt. Первый — это код выдачи токена, фрагмент выглядит следующим образом:
public String generateToken(Map<String, Object> claims, String subject) {
Date now = Calendar.getInstance().getTime();
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(new Date(System.currentTimeMillis() + expire * 1000))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
Среди них есть еще несколько важных параметров, которые необходимо пояснить:
-
subject
Выдающий принципал, например имя пользователя. На самом деле, это также размещено в исковых требованиях. -
claims
Некоторая дополнительная информация, то есть содержимое полезной нагрузки. Поскольку это HashMap, вы можете получить всю необходимую информацию в нем. -
expiration
Дата выдачи и срок действия, которые можно использовать во время проверки -
secret
Зашифрованный ключ, используемый для проверки ранее подписанного содержимого
Давайте также посмотрим на его проверочный код.
public Claims getClaims(String token){
Claims claims = Jwts
.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
return claims;
}
Как видите, мы также прошли черезsecret
, если этот секрет подделан, то этот код выдастSignatureException
аномальный.
Вот что такое jwt. Помните, что эти два метода, наша проверка интеграции, основаны на этих двух методах.
Интегрируйте его в проект SpringBoot
В системе SpringBoot наиболее часто используемой инфраструктурой аутентификации является собственная Spring Security. На самом деле сам jwt не сложен, сложность заключается в интеграции со Spring Security, то есть знания Spring Security больше.
Как показано на рисунке выше, мы распаковываем процесс использования Jwt на две части. Первая часть登录
, что можно сделать с помощью обычного контроллера. Вторая частьjwt验证
, мы используем метод перехватчика, чтобы решить его.
2. Конфигурация безопасности
Мы используемWebSecurityConfigurerAdapter
для завершения настройки Spring Security. Основной код разделен на 3 части.
Во-первых, источник пользовательских данных
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
Этот код настраивает источник пользовательских данных и алгоритм дайджеста для пароля. Здесь более высокий коэффициент безопасностиBCrypt
. То есть то, что мы сохраняем в MySQL, является паролем дайджеста BCrypt, а Spring Security рассчитает дайджест введенного нами пароля по этому алгоритму и сравнит его.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Мы имитируем реальный пользовательский источник данных, следующееJwtUserDetailsServiceImpl
код. Это означает, что пароли всех пользователей123456
.
JwtUserDetailsServiceImpl.java
@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService {
/**
* 已经在 WebSecurityConfig 中生成
*/
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, mockPassword(), getAuthorities());
}
private String mockPassword() {
return passwordEncoder.encode("123456");
}
private Collection<GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
authList.add(new SimpleGrantedAuthority("ROLE_USER"));
authList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return authList;
}
}
Во-вторых, конфигурация белого списка
Мы надеемся, что некоторые ссылки не проходят через перехватчики Spring Security, такие как swagger, такие как метод входа в систему, который требует игнорирования конфигурации в глобальной конфигурации.
Переопределите метод configure, чтобы игнорировать определенные ссылки.
String[] SWAGGER_WHITELIST = {
"/swagger-ui.html",
"/swagger-ui/**",
"/swagger-resources/**",
"/v2/api-docs",
"/v3/api-docs",
"/webjars/**"
};
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(SWAGGER_WHITELIST)
.antMatchers("/login**") ;
}
В-третьих, настройте фильтр
Конечно, есть и метод configure, на этот раз параметрHttpSecurity
. Мы добавляем пользовательские здесьJwtRequestFilter
прибытьUsernamePasswordAuthenticationFilter
До.
Фильтр, как правило, представляет собой модель цепочки ответственности, поэтому будут проблемы с заказом.
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors()
.and().csrf().disable()
.authorizeRequests()
.antMatchers(SWAGGER_WHITELIST).authenticated()
.anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(new JwtRequestFilter(), UsernamePasswordAuthenticationFilter.class);
}
Обратите внимание здесь. мы вaddFilterBefore
В этом методе настраиваемый фильтр является непосредственно новым. После тестирования, если вы этого не сделаете и передадите пользовательский фильтр Spring для управления, белый список, который мы настроили выше, будет недействительным, что является ловушкой.
На данный момент наша настройка Spring Security завершена. Давайте взглянем на фактический код входа и аутентификации.
3. Войти
Логин — это простой контроллер. Здесь я использую метод аутентификации AuthenticationManager для проверки имени пользователя и пароля. После прохождения проверки будет вызван метод jwt для генерации возврата токена.
Как видите, код для входа очень прост.
RestController
@CrossOrigin
public class LoginController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTools jwtTools;
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity<?> login(@RequestBody JwtRequest jwtRequest)
throws Exception {
Authentication authentication = authenticate(jwtRequest.getUsername(), jwtRequest.getPassword());
User user = User.class.cast(authentication.getPrincipal());
final String token = jwtTools.generateToken(new HashMap<>(), user.getUsername());
return ResponseEntity.ok(new JwtResponse(token));
}
private Authentication authenticate(String username, String password) throws Exception {
Objects.requireNonNull(username);
Objects.requireNonNull(password);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
}
Используйте swagger для вызова метода входа в систему. Когда введенный пароль равен 123456, он вернет нужный нам токен, в противном случае он вернет код состояния 403.
4. Проверка
Код подтверждения в основном помещается в фильтр, то, что мы наследуем,OncePerRequestFilter
, можно найти в егоdoFilterInternal
Напишите логику в методе.
Эта часть логики закодирована согласно картинке выше. Видно, что jwt — это лишь малая его часть.
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String token = request.getHeader("Authorization");
Claims claims = null;
try {
claims = getJwt().getClaims(token);
} catch (Exception ex) {
log.error("JWT Token error: {} , cause: {}", token, ex.getMessage());
}
if (claims == null) {
chain.doFilter(request, response);
return;
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
boolean ok = getJwt().validateTokenExpiration(claims);
if (ok) {
UserDetails userDetails = getUserDetailsService().loadUserByUsername(claims.getSubject());
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
chain.doFilter(request, response);
}
Таким образом, jwt и springboot могут быть идеально интегрированы. При настройке swagger мы обнаружили еще одну проблему: токен нужно вводить в информацию заголовка. Как это может быть хорошо.
По этому поводу Xiaoq также объяснил в предыдущей статье. У нас есть два способа настройки для завершения ввода токена разрешения swagger.
На рисунке ниже показан эффект конфигурации. вы можете обратиться к"Эта статья"для завершения настройки.
5. Это безопасно?
На снимке экрана, который мы только что начали, мы видим, что эту длинную строку информации jwt можно увидеть в виде открытого текста непосредственно на стороне клиента. Это позволяет легко заподозрить, что содержимое внутри может быть подделано?
Мы случайно скопировали его часть и расшифровали с помощью base64. Оказалось, что это действительно открытый текст.
На самом деле, если вы подделаете информацию открытого текста, а затем воспользуетесь кодировкой Base64 для ее повторного использования, она не пройдет проверку. Это то, что делает наш секретный ключ.
В Java этот ключ должен быть Base64 и может быть сгенерирован с помощью класса инструментов JDK.
String key = new String(Base64.getEncoder().encode("lk234jlk80234lsd可连接克里斯朵夫isofios23u8432ndsdfsokjjjsklfjslk%^&^&%$#$$%#83 12=12y3uiuy&^".getBytes()));
Этот ключ может быть очень сложным, и если он не утек, нашу информацию jwt очень трудно подделать. Таким образом, даже если вы видите открытый текст, вы не можете его изменить, так что это безопасно.
6. Проблемы в событиях jwt
Из приведенного выше описания видно, что нет проблем с использованием jwt для входа в систему. Его реализация проста и легко расширяема, и он может заменить файлы cookie и сеансы для выполнения функций, связанных с входом в систему и разрешениями. У него больше преимуществ. Во-первых, клиент может обрабатываться унифицированным образом, например, Android, IOS, Web и т. д. Кроме того, jwt поддерживает междоменное использование. Это относится к файлам cookie, потому что jwt может помещать информацию в заголовок или передавать ее напрямую в качестве параметра.
Есть и проблемы, сначала поговорим о производительности:
- занять пропускную способность.Потому что вам нужно передавать этот токен каждый раз. Обычно в полезной нагрузке мы храним только идентификатор пользователя, но если у вас есть больше атрибутов, таких как разрешения, строка станет очень большой. Если объем запросов относительно высок, эти накладные расходы довольно страшны.
- Информация о пользователе должна быть извлечена повторноБез сохранения состояния означает, что сервер не сохраняет информацию для входа, что требует получения информации о пользователе для каждого запроса. Как правило, извлекать эту информацию из базы данных нецелесообразно, вам нужен фронт кеша, такой как Redis. Но что бы вы ни делали, это не так быстро, как сессия. Таким образом, вы можете использовать кэш в куче для имитации набора сеансов.
Другие проблемы с использованием.
- Выход по токену.Если он просто без гражданства, будет очень сложно выйти из токена jwt. Если токен просочится, у вас даже не будет возможности предотвратить эти небезопасные запросы. Это связано с тем, что срок действия прописан в токене, и вы не можете его изменить.
- Обновление токена.Еще одна проблема — продление аренды токенов. Например, если срок действия вашего соглашения о токене истекает через полчаса, даже если вы выполните операцию на 29-й минуте, срок действия токена все равно истечет в оговоренное время. В глазах пользователя это будет выглядеть очень странно.
Чтобы решить эти проблемы, нам нужно поставить сервис无状态
Уплотните это свойство. После создания токена сохраните копию (redis, nosql и т. д.) на сервере, а затем обновите или выйдите из системы с этими токенами.
Но если вас не волнуют эти проблемы и если предположить, что токен очень безопасен и не будет утечек, вы можете выпустить токен с длительным тайм-аутом, генерировать его каждый раз при входе в систему и отбрасывать предыдущий токен. Таким образом, хотя эти старые жетоны существовали, о них больше никто не знал. Затем эти жетоны становятся призраками.
Суммировать
В этой статье кратко рассказывается о jwt, а затем в качестве примера рассматривается служба springboot, чтобы увидеть случай интеграции. Наконец, обсуждаются преимущества и недостатки jwt. Видно, что для обычного приложения jwt может добиться реальной развязки сервисов, а с токеном в качестве прохода можно переключаться между разными системами.
Опять же, полный пример кода для этой статьи можно найти в репозитории github.
https://github.com/yuanluoji/purestart-springboot-jwt
Если это поможет вам, пожалуйста, не забудьте проголосовать за меня. Ваша поддержка является движущей силой моего творчества, и в будущем будет больше высококачественного контента, которым я могу поделиться с вами.
Многие люди притворяются декадентами, и я советую вам не давать себя одурачить. Не отказывайтесь от каждой мысли о желании учиться, потому что это может быть вашим будущим «я», взывающим о помощи. Я Сяо Кью, и я буду прогрессировать вместе с вами. Нетрудно сдаться, но должно быть здорово упорствовать. .
Данная статья размещена от имени xjjdog.Для получения информации о springboot, проектах, личной жизни и т.д., пожалуйста, обратите внимание на паблик "Ape Logic"