Spring Boot 2.X Combat -- Вход и регистрация Spring Security (Token)

Spring Boot

Домашняя страница блога:me.csdn.net/u010974701

Репозиторий исходного кода:GitHub.com/Округ Чжашуй/…

В предыдущем разделе «Сражение Spring Boot 2.X — вход и регистрация в Spring Security» мы в основном интегрировали Spring Security для реализации регистрации пользователей, входа в систему и контроля разрешений.

В этом разделе мы реализуем контроль доступа к безопасности веб-приложений на основе токенов. Токен Токен — это метод аутентификации без сохранения состояния, он не сохраняет информацию об аутентификации пользователя на стороне сервера, как сеанс, и очень подходит для RESTful API.

Для веб-приложений с чистой аутентификацией Token общая идея такова: на основе предыдущего проекта закройте сеанс, который поставляется с Spring Security, чтобы разрешить междоменные запросы; добавьте перехватчик Token для перехвата всех запросов и проверки того, является ли Token действительный.

Если вы хотите реализовать Session + Token, моя идея состоит в том, чтобы добавить перехватчики Session и Token для перехвата всех запросов. Аутентификация и авторизация сеанса используются для запросов с файлами cookie; запросы без файлов cookie передаются перехватчику Token для аутентификации и авторизации. (Эта идея еще не была реализована, хи-хи, если вы можете, оставьте сообщение и сообщите Xiaoxian).

В этом разделе вместо использования шаблона MVC MySQL и MyBatis используются для создания реализации RESTful API.

1) Что такое токен

1.1) Как сервер узнает, вошел ли пользователь в систему?

Мы знаем, что сам протокол HTTP не включает состояние, и каждый раз, когда HTTP-связь между клиентом и сервером завершается, она отключается, поэтому HTTP не запоминает, кто является вошедшим в систему пользователем.

Чтобы решить ситуацию, когда каждый запрос должен приводить имя пользователя и пароль пользователя для аутентификации пользователя, обычно существуют следующие два метода, каждый из которых имеет свои преимущества и недостатки и может быть выбран в соответствии с фактической ситуацией. проект.

Метод аутентификации на основе сеанса

1. После того, как пользователь входит в систему, информация о входе пользователя (сеанс) создается на стороне сервера, и идентификатор (ID сеанса) информации для входа возвращается клиенту. Клиенты обычно сохраняются с помощью файлов cookie.

2. Клиенту нужно только передать идентификатор регистрационной информации (Session_ID) на сервер для каждого запроса, чтобы выяснить, существует ли соответствующая регистрационная информация (Session).

3. Если информация для входа (Сеанс) найдена, это означает, что проверка пройдена.

Для одного приложения-службы нет проблем, если вы хотите масштабироваться до распределенного приложения. Например, у компании есть две службы, A и B, и ей необходимо понимать, что пользователи могут получить доступ к обеим службам после входа в любую службу. Идея реализации состоит в том, чтобы сохранить сеанс, например, записать в базу данных или кеш Redis, перейти к базе данных или кешу Redis, чтобы найти сеанс во время проверки, регулярно очищать базу данных или устанавливать время истечения срока действия Redis.

Метод аутентификации на основе токенов

В этом методе информация для входа пользователя не хранится на сервере, а токен пользователя хранится на клиенте. Клиент запрашивает информацию о токене, и сервер проверяет действительность токена.

1. Пользователь вводит имя пользователя и пароль пользователя, а после проверки шифрует и генерирует Токен

2. Клиент сохраняет Токен и приносит этот Токен с каждым запросом

3. Сервер проверяет валидность Токена по ключу

Если две службы A и B используют один и тот же алгоритм шифрования и ключ, то токен, сгенерированный любой службой, можно использовать непосредственно в другой службе, даже если добавляется новая служба C, при условии, что алгоритм шифрования и ключ являются то же самое, три токена каждой службы по-прежнему распространены.

Для необходимости аннулировать токен с неистекшим сроком действия после того, как пользователь изменит пароль, вы можете добавить отметку времени для пользователя, чтобы изменить информацию в пользовательской таблице.Пока токен генерируется до этой отметки времени, он будет недействителен по умолчанию; есть еще один метод.Добавьте кеш Redis и удалите кеш токена пользователя после изменения информации о пользователе.Если в кеше нет токена пользователя, он будет недействителен по умолчанию.

Что касается метода настройки единого входа, я сейчас думаю только о кеше Redis (вручную закрой лицо)

1.2) Как выглядит токен?

Токен состоит из трех частей

  • Заголовок
  • Полезная нагрузка
  • Подпись

Полный пример токена с тремя частями, разделенными десятичной точкой.:

eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJTcHJpbmdCb290IiwiYXVkIjoiYm9vdCIsImV4cCI6MTU4NjE5MzMwMywiaWF0IjoxNTg1NTg4NTAzLCJyb2xlIjoiQURNSU4ifQ.SdHSoet9BEaMcBrbbwO4_nd88nO7VIuV6IB_Kdw1AFmmPPCxY8CKUoE-QrJmN3RSMAdxLB0GAfDiwFV6zpxVZksXZQAQzxa_bPw0JUj7mZyHzdSR2jNm_oKB_2rnRsfW7caXZVgwtUU2lHoXSLdlgHRqoyIw7AcP5dm-og3ELGgUQxa27mmwvXtRngfvgw1EKoeA_bdwNSbDWu8clyNjd9ftq9_yU3QKFc3NAUVkWTRa8U1_dyOI9B4LMrKrXEQSR8D7UDw-0MDbOZNwzUmxv0h-QER1cw5dxnQsMs2C9TI32x9E68PaNC8PkaAyOkCs55y-W7wyf-K24fzt5nQb4w

должны знать о том,Заголовок и полезная нагрузка не шифруются, а данные в формате JSON кодируются алгоритмом Bas64, поэтому пароль и информация о конфиденциальности пользователя не могут храниться в токене..

Заголовок

{
  "alg": "HS256"
}

Заголовок обычно представляет собой алгоритм шифрования, который объявляет подпись токена. После кодирования Base64 получается первая часть токена. Пример выглядит следующим образом:

eyJhbGciOiJIUzI1NiJ9

Полезная нагрузка

{
  "sub": "Joe"
}

Это основная часть Токена, а вот вторая часть Токена, полученная после кодирования Base64, пример такой:

eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4

Следует отметить, что полезная нагрузка не зашифрована, пожалуйста, не храните здесь какую-либо личную информацию, такую ​​как пароли, информацию о конфиденциальности пользователей.

Обычно используются следующие настройки, стандартные заявления:

  • setIssuer: Эмитент токена
  • setSubject: Тема токена
  • setAudience: получатель токена
  • setExpiration: Дата истечения срока годности
  • setNotBefore: После какой даты вступит в силу
  • setIssuedAt: время выпуска
  • setId: уникальный идентификатор

Пользовательские претензии:

  • .claim("role", "ADMIN"): с помощью этого метода настраивается роль Claims и значение ADMIN.

Подпись

Часть подписи предназначена для токенаЗаголовокиПолезная нагрузкаГенерируется методом шифрования, объявленным в заголовке, и ключом шифрования, определенным сервером, еслиЗаголовоки изменения данных **Payload**,ПодписьСразу после смены, в случае, если ключ не утек, другие людиНевозможно подделать или подделать токен, поэтому Token безопасен.

SdHSoet9BEaMcBrbbwO4_nd88nO7VIuV6IB_Kdw1AFmmPPCxY8CKUoE-QrJmN3RSMAdxLB0GAfDiwFV6zpxVZksXZQAQzxa_bPw0JUj7mZyHzdSR2jNm_oKB_2rnRsfW7caXZVgwtUU2lHoXSLdlgHRqoyIw7AcP5dm-og3ELGgUQxa27mmwvXtRngfvgw1EKoeA_bdwNSbDWu8clyNjd9ftq9_yU3QKFc3NAUVkWTRa8U1_dyOI9B4LMrKrXEQSR8D7UDw-0MDbOZNwzUmxv0h-QER1cw5dxnQsMs2C9TI32x9E68PaNC8PkaAyOkCs55y-W7wyf-K24fzt5nQb4w

JJWT — это набор инструментов с открытым исходным кодом (Apache 2.0) для создания и проверки веб-токенов JSON (JWT) на JVM и Android, а также реализация Java, основанная на спецификациях JWT, JWS, JWE, JWK. Поддерживаемые алгоритмы шифрования: HMAC, RSASSA, ECDSA, и во время разработки необходимо использовать ключ, достаточно надежный для выбранного алгоритма. В этом разделе для шифрования токена будет использоваться асимметричное шифрование RSA.

2) Введение зависимости JJWT и конфигурация проекта

Создайте новый проект 06-security-token,Обратите внимание, что версия Spring Boot должна быть версии 2.1.X., не забудьте проверить Spring Security, MySQL, MyBatis, веб-зависимости.

Для проектов Maven соответствующая конфигурация зависимостей также приведена в конце статьи.

Конфигурация проекта Gradle

    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.2'
    runtimeOnly 'mysql:mysql-connector-java'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    // 添加如下的依赖 https://github.com/jwtk/jjwt
    compile 'io.jsonwebtoken:jjwt-api:0.11.1'
    runtime 'io.jsonwebtoken:jjwt-impl:0.11.1',
            // Uncomment the next line if you want to use RSASSA-PSS (PS256, PS384, PS512) algorithms:
            //'org.bouncycastle:bcprov-jdk15on:1.60',
            // or 'io.jsonwebtoken:jjwt-gson:0.11.1' for gson
            'io.jsonwebtoken:jjwt-jackson:0.11.1'

1.2) Конфигурация проекта

Настройте базу данных MySQL и преобразование верблюжьего регистра MyBatis, application.properties

# 数据库 URL、用户名、密码、JDBC Driver更换数据库只需更改这些信息即可
# MySQL 8 需要指定 serverTimezone 才能连接成功
spring.datasource.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.password=xiaoxian
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MyBatis 驼峰命名转换
mybatis.configuration.map-underscore-to-camel-case=true

Добавить @MapperScan

@MapperScan("org.xian.security.mapper")
public class SecurityApplication {}

2) Начните использовать JJWT

Основная структура проекта:

  • пакет контроллера: интерфейс API
  • пакет услуг: предоставляет интерфейсные услуги для API
  • пакет mapper: класс MyBatis Mapper
  • пакет сущностей: класс сущностей
  • пакет безопасности: проверка перехвата токена, генерация токена, конфигурация Spring Security

MyResponse : public Response возвращает класс сообщения:

public class MyResponse implements Serializable {
    private static final long serialVersionUID = -2L;
    private String status;
    private String message;
}

2.1) Классы сущностей Entity и Mapper

Структура таблицы здесь такая же, как и в предыдущем разделе, пользовательская таблица sys_user

поле тип Примечание
user_id bigint автоматическое увеличение первичного ключа
username varchar(18) Имя пользователя, ненулевое и уникальное
password varchar(60) пароль, не пустой
user_role varchar(8) Роли пользователей (ПОЛЬЗОВАТЕЛЬ / АДМИНИСТРАТОР)

Роли пользователей здесь USER/ADMIN, ситуация, когда у пользователя может быть несколько ролей, пока не рассматривается.

SQL

use spring;
create table sys_user
(
    user_id   bigint auto_increment,
    username  varchar(18)  not null unique,
    password  varchar(60) not null,
    user_role varchar(8)   not null,
    constraint sys_user_pk
        primary key (user_id)
);

Класс сущностей: Создайте новый пакет с именем entity . Создайте новый класс SysUser под сущностью:

public class SysUser implements Serializable {
    private static final long serialVersionUID = 4522943071576672084L;
    private Long userId;
    private String username;
    private String password;
    private String userRole;
    // 省略 getter setter constructor
}

Класс интерфейса картографа: Новый сопоставитель пакетов, новый класс SysUserMapper:

// 这里使用注解的方式
public interface SysUserMapper {
    /** 往 sys_user 插入一条记录
     * @param sysUser 用户信息
     */
    @Insert("Insert Into sys_user(username, password,user_role) Values(#{username}, #{password},#{userRole})")
    @Options(useGeneratedKeys = true, keyProperty = "userId")
    void insert(SysUser sysUser);

    /** 根据用户 Username 查询用户信息
     * @param username 用户名
     * @return 用户信息
     */
    @Select("Select user_id,username, password,user_role From sys_user Where username=#{username}")
    SysUser selectByUsername(String username);
}

2.2) Конфигурация токена

Он состоит из следующих частей

  • Класс инструмента открытого ключа RSA key
  • Класс инструментов генерации и проверки токенов
  • Перехватчик токенов, перехватывает все запросы и проверяет, что токен действителен.
  • Custom не реализует класс обработки ошибок авторизованного доступа
  • Пользовательская реализация интерфейса UserDetailsService
  • Конфигурация безопасности Spring

Создайте новый класс RsaUtils в пакете Security, класс инструмента для открытых и секретных ключей RSA. Обратите внимание, что в JDK 8 2048-битные ключи не поддерживаются.

public class RsaUtils {
    /** PrivateKey * 生成秘钥 > openssl genrsa -out rsa_private_key.pem 2048 
     * 转换成PKCS8格式 >openssl pkcs8 -topk8 -inform * PEM -in rsa_private_key.pem -outform PEM -nocrypt 
     * 在终端输出结果,去掉“-----BEGIN PRIVATE KEY-----” * “-----END PRIVATE KEY-----”
     * @return PrivateKey
     */
    public static PrivateKey getPrivateKey() {
        PrivateKey privateKey = null;
        try {
            String privateKeyStr = "PrivateKey";
            // PKCS8格式的密钥
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyStr));
            // RSA 算法
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return privateKey;
    }

    /** PublicKey 根据 秘钥 生成public key > openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
     * @return PublicKey
     */
    public static PublicKey getPublicKey() {
        PublicKey publicKey = null;
        try {
            String publicKeyStr = "public key";
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyStr));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            e.printStackTrace();
            return null;
        }
    }
}

TokenUtils: служебный класс для создания и проверки токенов. Необязательная часть тела Token означает, что эта информация не используется для аутентификации и авторизации:

@Component
public class TokenUtils implements Serializable {
    private static final long serialVersionUID = -3L;
    /**
     * Token 有效时长
     */
    private static final Long EXPIRATION = 604800L;

    /** 生成 Token 字符串 必须 setAudience 接收者 setExpiration 过期时间 role 用户角色
     * @param sysUser 用户信息
     * @return 生成的Token字符串 or null
     */
    public String createToken(SysUser sysUser) {
        try {
            // Token 的过期时间
            Date expirationDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000);
            // 生成 Token
            String token = Jwts.builder()
                    // 设置 Token 签发者 可选
                    .setIssuer("SpringBoot")
                    // 根据用户名设置 Token 的接受者
                    .setAudience(sysUser.getUsername())
                    // 设置过期时间
                    .setExpiration(expirationDate)
                    // 设置 Token 生成时间 可选
                    .setIssuedAt(new Date())
                    // 通过 claim 方法设置一个 key = role,value = userRole 的值
                    .claim("role", sysUser.getUserRole())
                    // 设置加密密钥和加密算法,注意要用私钥加密且保证私钥不泄露
                    .signWith(RsaUtils.getPrivateKey(), SignatureAlgorithm.RS256)
                    .compact();
            return String.format("Bearer %s", token);
        } catch (Exception e) {
            return null;
        }
    }

    /** 验证 Token ,并获取到用户名和用户权限信息
     * @param token Token 字符串
     * @return sysUser 用户信息
     */
    public SysUser validationToken(String token) {
        try {
            // 解密 Token,获取 Claims 主体
            Claims claims = Jwts.parserBuilder()
                    // 设置公钥解密,以为私钥是保密的,因此 Token 只能是自己生成的,如此来验证 Token
                    .setSigningKey(RsaUtils.getPublicKey())
                    .build().parseClaimsJws(token).getBody();
            assert claims != null;
            // 验证 Token 有没有过期 过期时间
            Date expiration = claims.getExpiration();
            // 判断是否过期 过期时间要在当前日期之后
            if (!expiration.after(new Date())) {
                return null;
            }
            SysUser sysUser = new SysUser();
            sysUser.setUsername(claims.getAudience());
            sysUser.setUserRole(claims.get("role").toString());
            return sysUser;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

TokenFilter: Перехватчик токенов, который перехватывает все запросы и проверяет, действителен ли токен. Если он действителен, авторизация проходит. Если он недействителен, Spring Security будет перехватывать недопустимые запросы в соответствии с конфигурацией:

@SuppressWarnings("SpringJavaAutowiringInspection")
@Service
public class TokenFilter extends OncePerRequestFilter {
    @Resource
    TokenUtils tokenUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 存储 Token 的 Headers Key与 Value,默认是 Authorization
        final String authorizationKey = "Authorization";
        String authorizationValue;
        try {
            authorizationValue = request.getHeader(authorizationKey);
        } catch (Exception e) {
            authorizationValue = null;
        }
        // Token 开头部分 默认 Bearer 开头
        String bearer = "Bearer ";
        if (authorizationValue != null && authorizationValue.startsWith(bearer)) {
            // token
            String token = authorizationValue.substring(bearer.length());
            SysUser sysUser = tokenUtils.validationToken(token);
            if (sysUser != null) {
                // Spring Security 角色名称默认使用 "ROLE_" 开头
                // authorities.add 可以增加多个用户角色,对于一个用户有多种角色的系统来说,
                // 可以通过增加用户角色表、用户--角色映射表,存储多个用户角色信息
                List<SimpleGrantedAuthority> authorities = new ArrayList<>();
                authorities.add(new SimpleGrantedAuthority("ROLE_" + sysUser.getUserRole()));
                // 传入用户名、用户密码、用户角色。 这里的密码随便写的,用不上
                UserDetails userDetails = new User(sysUser.getUsername(), "password", authorities);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(userDetails.getUsername());
                // 授权
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        filterChain.doFilter(request, response);
    }
}

ErrorAuthenticationEntryPoint: Класс сообщения об ошибке, неавторизованный доступ возвращает 401 информацию о неавторизованном доступе через этот класс. Пользовательская реализация стандартного интерфейса обработки несанкционированного доступа Spring Security AuthenticationEntryPoint:

@Component
public class ErrorAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
    private static final long serialVersionUID = 5200068540912465653L;
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        // 设置 Json 格式返回
        response.setContentType("application/json;charset=UTF-8");
        // 设置 HTTP 状态码为 401
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // PrintWriter 输出 Response 返回信息
        PrintWriter writer = response.getWriter();
        ObjectMapper mapper = new ObjectMapper();
        MyResponse myResponse = new MyResponse("error", "非授权访问");
        // 将对象输出为 JSON 格式。可以通过重写 MyResponse 的 toString() ,直接通过 myResponse.toString() 即可
        writer.write(mapper.writeValueAsString(myResponse));
    }
}

UserDetailsServiceImpl: Пользовательская реализация UserDetailsService путем переопределения метода loadUserByUsername интерфейса UserDetailsService для передачи имени пользователя, пароля пользователя и роли пользователя в Spring Security.

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource
    private SysUserMapper sysUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserMapper.selectByUsername(username);
        if (sysUser == null ) {
            throw new UsernameNotFoundException(username);
        }
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        // Spring Security 角色名称默认使用 "ROLE_" 开头
        // authorities.add 可以增加多个用户角色,对于一个用户有多种角色的系统来说,
        // 可以通过增加用户角色表、用户--角色映射表,存储多个用户角色信息
        authorities.add(new SimpleGrantedAuthority("ROLE_" + sysUser.getUserRole()));
        // 给 Spring Security 传入用户名、用户密码、用户角色。
        return new User(sysUser.getUsername(), sysUser.getPassword(), authorities);
    }
}

SpringSecurityConfig: Конфигурация Spring Security, настройка алгоритма шифрования хранилища паролей, добавление перехватчика, закрытие диспетчера сеансов, разрешение междоменного доступа, разрешение API входа и регистрации без авторизации.

@SuppressWarnings("SpringJavaAutowiringInspection")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserDetailsServiceImpl userDetailsService;

    @Resource
    private ErrorAuthenticationEntryPoint errorAuthenticationEntryPoint;

    @Resource
    private TokenFilter tokenFilter;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        // 使用 BCryptPasswordEncoder 验证密码
        authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // BCrypt 密码
        return new BCryptPasswordEncoder();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        // 配置 CSRF 关闭,允许跨域访问
        httpSecurity.csrf().disable();
        // 指定错误未授权访问的处理类
        httpSecurity.exceptionHandling().authenticationEntryPoint(errorAuthenticationEntryPoint);
        // 关闭 Session
        httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 允许 登录 注册的 api 的无授权访问,其他需要授权访问
        httpSecurity.authorizeRequests()
                .antMatchers("/api/user/login", "/api/user/register")
                .permitAll().anyRequest().authenticated();
        // 添加拦截器
        httpSecurity.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 禁用缓存
        httpSecurity.headers().cacheControl();
    }
}

На этом настройка безопасности токена завершена.

2.3) Интерфейс API и сервисный уровень интерфейса

Создайте новый пакет службы и создайте новый слой службы интерфейса SysUserService:

@Service
public class SysUserService {
    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private TokenUtils tokenUtils;

    @Resource
    private SysUserMapper sysUserMapper;

    /** 用户登录
     * @param sysUser 用户登录信息
     * @return 用户登录成功返回的Token
     */
    public MyResponse login(final SysUser sysUser) {
        try {
            // 验证用户名和密码是否对的
            System.out.println(sysUser.getUsername());
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(sysUser.getUsername(),
                            sysUser.getPassword()));
        } catch (BadCredentialsException e) {
            return new MyResponse("ERROR", "用户名或者密码不正确");
        }
        // 生成Token与查询用户权限
        SysUser sysUserData = sysUserMapper.selectByUsername(sysUser.getUsername());
        return new MyResponse("SUCCESS",
                tokenUtils.createToken(sysUserData));
    }

    /** 用户注册
     * @param sysUser 用户注册信息
     * @return 用户注册结果
     */
    public MyResponse save(SysUser sysUser) throws DataAccessException {
        try {
            // 密码加密存储
            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
            String password = bCryptPasswordEncoder.encode(sysUser.getPassword());
            sysUser.setPassword(password);
            sysUserMapper.insert(sysUser);
        } catch (DataAccessException e) {
            return new MyResponse("ERROR", "已经存在该用户名或者用户昵称,或者用户权限出错");
        }
        return new MyResponse("SUCCESS", "用户新增成功");
    }
}

SysUserController: Интерфейс RESTful API, @PreAuthorize("hasRole('ADMIN')") указывает, что только пользователи с полномочиями ADMIN могут получить доступ

@RestController
@RequestMapping(value = "/api/user")
public class SysUserController {
    @Resource
    private SysUserService sysUserService;

    /** 用户登录接口
     * @param sysUser 用户登录的用户名和密码
     * @return 用户Token和角色
     * @throws AuthenticationException 身份验证错误抛出异常
     */
    @PostMapping(value = "/login")
    public MyResponse login(@RequestBody final SysUser sysUser) throws AuthenticationException {
        return sysUserService.login(sysUser);
    }

    /** 用户注册接口
     * @param sysUser 用户注册信息
     * @return 用户注册结果
     */
    @PostMapping(value = "/register")
    public MyResponse register(@RequestBody @Valid final SysUser sysUser) {
        return sysUserService.save(sysUser);
    }

    /** 这是登录用户才可以看到的内容 */
    @PostMapping(value = "/message")
    public String message() {
        return "这个消息只有登录用户才可以看到";
    }

    /** 这是管理员用户才可以看到 */
    @PostMapping(value = "/admin")
    @PreAuthorize("hasRole('ADMIN')")
    public String admin() {
        return "这个消息只有管理员用户才可以看到";
    }
}

2.4) Бежать

Запустите проект, чтобы зарегистрироваться, войти в систему и получить доступ к пользовательскому интерфейсу авторизации соответственно.

регистр:Зарегистрируйте пользователей с правами ADMIN и USER соответственно.

Авторизоваться:

Авторизоваться с помощью токена: /api/user/admin и /api/user/message

Приложение, конфигурация проекта Maven

<dependencies>
    <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.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.1</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.1</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>        <!-- or jjwt-gson if Gson is preferred -->
        <version>0.11.1</version>
        <scope>runtime</scope>
    </dependency>
    <!-- Uncomment this next dependency if you are using JDK 10 or earlier and you also want to use 
RSASSA-PSS (PS256, PS384, PS512) algorithms.  JDK 11 or later does not require it for those algorithms:
<dependency>
<groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.60</version>
<scope>runtime</scope>
</dependency> -->
</dependencies>

Этот раздел в основном посвящен интеграции Spring Boot с интерфейсом Spring Security, JJWT, RESTful API для аутентификации и авторизации токена, а также функциям регистрации пользователей, входа в систему и управления ролями. В следующем разделе мы интегрируем другую часто используемую структуру безопасности с Spring Boot.Apache ShiroИнтерфейс RESTful API, который реализует аутентификацию и авторизацию с помощью токена. Следующая последовательность статей организована

  • Apache Shiro реализует RESTful API для аутентификации и авторизации токенов.
  • Apache Shiro реализует вход в систему с помощью скан-кода WeChat