Прежде чем читать эту статью, мы также должны иметь общее представление о сеансах, файлах cookie и JWT. В этой статье Xiaomazai не будет повторять их слишком много.Если вы недостаточно четко знаете эти три, вы можете сначала перейти сюда:После прочтения этого Session, Cookie, Token не проблема поспорить с интервьюеромБазовое понимание и понимание этого.
Если у вас есть базовые концепции и понимание трех вышеперечисленных, но все еще полны сомнений по поводу использования JWT, то эта статья написана для вас. В этой статье мы будем использовать SpringBoot для интеграции JWT для реализации простой проверки токена, чтобы у нас было общее представление об использовании JWT.
SpringBoot интегрирует JWT
Сначала мы создаем среду SpringBoot, и среда SpringBoot готова. Далее сделайте следующее:
1. Введите зависимости
Введите зависимости JWT. Поскольку он основан на Java, необходимоjava-jwt
.
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.5.0</version>
</dependency>
2. Пользовательские аннотации
На этом шаге мы определяем аннотацию в пакете аннотаций, который пользователь должен использовать для входа в систему для доступа к другим интерфейсам и другим операциям.TokenRequired
.
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenRequired {
boolean required() default true;
}
@Target
Намерение настроить аннотации для нас@TokenRequired
Цель действия, потому что целью нашей аннотации на этот раз является уровень метода, поэтому используйтеElementType.METHOD
.
@Retention
Намерение настроить аннотации для нас@TokenRequired
Зарезервированная позиция,@TokenRequired
Позиция определена как зарезервированнаяRetentionPolicy.RUNTIME
Аннотации этого типа будут зарезервированы JVM и могут быть прочитаны и использованы JVM или другим кодом, использующим отражение во время выполнения.
3. Определите класс сущности
В пакете сущностей мы используем ломбок, чтобы просто настроить пользователя класса сущностей.
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
String Id;
String username;
String password;
}
4. Определите класс инструмента JWT
На этом этапе мы создаем класс инструмента JwtUtil в пакете util для создания токенов и проверки токенов.
public class JwtUtil {
//过期时间15分钟
private static final long EXPIRE_TIME = 15*60*1000;
//生成签名,15分钟后过期
public static String sign(String username,String userId,String password){
//过期时间
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
//使用用户密码作为私钥进行加密
Algorithm algorithm = Algorithm.HMAC256(password);
//设置头信息
HashMap<String, Object> header = new HashMap<>(2);
header.put("typ", "JWT");
header.put("alg", "HS256");
//附带username和userID生成签名
return JWT.create().withHeader(header).withClaim("userId",userId)
.withClaim("username",username).withExpiresAt(date).sign(algorithm);
}
//校验token
public static boolean verity(String token,String password){
try {
Algorithm algorithm = Algorithm.HMAC256(password);
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(token);
return true;
} catch (IllegalArgumentException e) {
return false;
} catch (JWTVerificationException e) {
return false;
}
}
}
5. Проверка бизнеса и генерация токенов
В пакете службы мы создаем UserService и определяем метод входа в систему для проверки данных бизнес-уровня интерфейса входа в систему и вызываем метод в JwtUtil для создания токена.
@Service("UserService")
public class UserService {
@Autowired
UserMapper userMapper;
public String login(String name, String password) {
String token = null;
try {
//校验用户是否存在
User user = userMapper.findByUsername(name);
if(user == null){
ResultDTO.failure(new ResultError(UserError.EMP_IS_NULL_EXIT));
}else{
//检验用户密码是否正确
if(!user.getPassword().equals(password)){
ResultDTO.failure(new ResultError(UserError.PASSWORD_OR_NAME_IS_ERROR));
}else {
// 生成token,将 user id 、userName保存到 token 里面
token = JwtUtil.sign(user.getUsername(),user.getId(),user.getPassword());
}
}
} catch (Exception e) {
e.printStackTrace();
}
return token;
}
}
Algorithm.HMAC256()
:использоватьHS256
генерироватьtoken
, ключ является паролем пользователя, и единственный ключ может храниться на сервере.
withAudience()
Депозит должен быть сохранен вtoken
информация, здесь я храню идентификатор пользователя вtoken
середина.
6. Определите перехватчик
Далее нам нужно написать перехватчик для получения токена и проверки токена.
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
// 从 http 请求头中取出 token
String token = httpServletRequest.getHeader("token");
// 如果不是映射到方法直接通过
if(!(object instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)object;
Method method=handlerMethod.getMethod();
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(TokenRequired.class)) {
TokenRequired userLoginToken = method.getAnnotation(TokenRequired.class);
if (userLoginToken.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
// 获取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getClaim("userId").asString();
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("用户不存在,请重新登录");
}
// 验证 token
try {
if(!JwtUtil.verity(token,user.getPassword())){
throw new RuntimeException("无效的令牌");
}
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
AuthenticationInterceptor
Перехватчик реализованHandlerInterceptor
Три метода интерфейса:
boolean preHandle ():
Метод обратного вызова предварительной обработки реализует предварительную обработку процессора. Третий параметр — это процессор ответа и возвращаемое значение пользовательского контроллера. Возвращаемое значение true вызовет следующий перехватчик или процессор или затем выполнит postHandle() и afterCompletion(); false указывает на то, что процесс прерван, и не будет продолжать вызывать другие перехватчики или процессоры, прерывая выполнение.
void postHandle():
Метод обратного вызова постобработки реализует постобработку процессора (DispatcherServlet вызывается перед возвратом и визуализацией представления) В это время мы можем обрабатывать данные модели или представление через modelAndView, а modelAndView также может быть null .
void afterCompletion():
Метод обратного вызова после обработки всего запроса Этот метод также выполняется только тогда, когда возвращаемое значение preHandle() текущего соответствующего Interceptor равно true, то есть после того, как DispatcherServlet отобразит соответствующее представление. Используется для очистки ресурсов.
Перехватчик потока выполнения:
- Удален из токена первого http-запроса;
- Проверьте, есть ли какие-либо аннотации, требующие разрешений пользователя, и если да, проверьте, не пуст ли токен;
- Если токен не пуст, запросите информацию о пользователе и проверьте токен;
- Если проверка пройдена, будет выполнена обработка бизнес-доступа, а если проверка не пройдена, будет возвращена информация о недействительности токена.
7. Настройте перехватчик
Добавлены аннотации к классам конфигурации@Configuration
, указывая, что класс является классом конфигурации и добавит класс как SpringBean в контейнер IOC.
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 将我们上步定义的实现了HandlerInterceptor接口的拦截器实例authenticationInterceptor添加InterceptorRegistration中,并设置过滤规则,所有请求都要经过authenticationInterceptor拦截。
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**");
}
}
WebMvcConfigurer
Интерфейс — это метод конфигурации внутри Spring, который использует форму JavaBean для замены традиционного файла конфигурации xml для удовлетворения основных потребностей конфигурации.
InterceptorConfig
внутриaddInterceptor
нужна реализацияHandlerInterceptor
экземпляр перехватчика для интерфейса,addPathPatterns
Метод используется для установки правил пути фильтрации перехватчика.
существуетaddInterceptors
метод, мы реализовали реализацию, определенную на шаге 6HandlerInterceptor
Примеры интерфейса перехватчикаauthenticationInterceptor
, добавленInterceptorRegistration
и задайте путь к фильтру. Теперь все наши запросы проходятauthenticationInterceptor
перехватчикauthenticationInterceptor
пройти черезpreHandle
Бизнес-фильтр метода для определения наличия@TokenRequired
чтобы решить, нужно ли вам войти в систему.
8. Определите методы интерфейса и добавьте аннотации
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
UserService userService;
/**
* 用户登录
* @param user
* @return
*/
@PostMapping("/login")
public ResultDTO login( User user){
String token = userService.login(user.getUsername(), user.getPassword());
if (token == null) {
return ResultDTO.failure(new ResultError(UserError.PASSWORD_OR_NAME_IS_ERROR));
}
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put("token", token);
return ResultDTO.success(tokenMap);
}
@TokenRequired
@GetMapping("/hello")
public String getMessage(){
return "你好哇,我是小码仔";
}
}
Если аннотация не добавлена, проверка по умолчанию не выполняется, и интерфейс входа обычно не проверяется. так что яgetMessage()
В интерфейс добавляется аннотация для входа в систему, указывающая, что для получения токена необходимо войти в интерфейс, а затем токен можно добавить в заголовок запроса и проверить, прежде чем к нему можно будет получить доступ.
запросить подтверждение
я прав в кодеgetMessage()
Добавлен@TokenRequired
Обратите внимание, что при доступе к этому методу в этот момент значение токена должно быть получено путем входа в систему, и токен может быть добавлен в заголовок запроса, прежде чем к нему можно будет получить доступ. Теперь делаем следующие проверки:
- Прямой доступ без добавления токена в заголовок запроса:
Как показано на рисунке выше, результат запроса показывает: Нет токена, пожалуйста, войдите снова.
- Получите доступ к интерфейсу входа, получите токен и добавьте информацию о токене в заголовок запроса:
В этот момент доступ успешен.
- Через 15 минут токен недействителен, и мы снова добавляем доступ к информации о токене в заголовок запроса:
На данный момент токен недействителен и возвращает: недопустимый токен.
Суммировать
Просмотрите основной процесс бизнес-оценки, используемый в этом JWT:
- Когда пользователь обращается к странице, интерфейсные запросы запросы соответствующего интерфейса пропускают через перехватчик, и перехватчик вынимает токен из заголовка HTTP-запроса;
- Проверяем, есть ли в интерфейсе аннотация @TokenRequired, если нет, то отпускаем сразу, если да, то проверяем, не пуст ли токен;
- Если токен пуст, доступ невозможен; если токен не пуст, запросите информацию о пользователе и проверьте токен;
- Если проверка пройдена, будет выполнена обработка бизнес-доступа, а если проверка не пройдена, будет возвращена информация о недействительности токена.
Недостатки: Эта интеграция представляет собой простое введение в использование JWT, а механизм обновления истечения срока действия токена не реализован. В этом случае пользователю необходимо снова входить в систему каждые 15 минут. Если он используется в реальном производстве среде могут быть убиты пользователи, поэтому в фактической разработке это не рекомендуется.
Что касается механизма обновления токена, Xiaomazai интерпретирует и приложит исходный код для вас в следующей статье.
Этот интегрированный код Адрес: https://github.com/bailele1995/springboot-jjwt.git
Наконец
Наконец, спасибо за чтение. Цель статьи — зафиксировать и поделиться, если в статье есть явные ошибки, пожалуйста, указывайте на них, и мы будем учиться вместе в ходе обсуждения. Большое тебе спасибо !
Если вы считаете, что эта статья полезна для вас, то поставьте лайк, ваша поддержка и поддержка являются для меня движущей силой двигаться вперед~
Добро пожаловать, чтобы обратить внимание на мой публичный аккаунт WeChat [маленький программист], давайте вместе обсудим код и жизнь.
Ссылка на статью:
https://my.oschina.net/odetteisgorgeous/blog/1920762
https://www.zhihu.com/search?type=content&q=spring%20boot%20jwt
https://mp.weixin.qq.com/s/q4upZNTul5Z5WSq2wHC9lg
https://mp.weixin.qq.com/s/Vh-75A7qN8lDo_0DQXCkzg
https://mp.weixin.qq.com/s/KlXc5hWEfgj-Q9cMabeOdA
https://juejin.cn/post/6844904034181070861