Сводка использования Spring Security

сервер Spring Безопасность API

Во время летних каникул я изучил Spring Security и успешно применил его в проекте. На практике я нашел набор разрешений, сочетающих json + jwt (веб-токен json) + Spring Boot + технологию Spring Security для записи во время праздника Национального дня.

Исходный код для всех описанных ниже шагов можно загрузить с моегоgithubполучено на. Прочтите readme.md, если хотите знать.

Краткое введение в каждую технологию

json: формат обмена данными для взаимодействия с интерфейсом

Лично для него характерно содействие разделению передней и задней частей сети и повышение эффективности работы команды. В то же время это еще и инструмент для взаимодействия с Android и iOS.В настоящее время я не думал о другой форме общения, кроме json и XML (возможно, я посмотрю на это, когда у меня будет свободное время в будущем) .

Еще одна его особенность заключается в том, что он легкий, краткий и четкий, слои могут облегчить нам чтение и запись и уменьшить использование пропускной способности сервера.

jwt (json web token)

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

Некоторые преимущества взяты из Интернета, и читатели, заинтересованные в получении дополнительной информации о jwt, могут самостоятельно найти ее в Google:

  1. По сравнению с сеансом его не нужно хранить на сервере, и он не занимает дополнительную память сервера.
  2. Без сохранения состояния и с высокой степенью масштабируемости: например, есть 3 компьютера (A, B, C) для формирования кластера серверов. Если сеанс существует на компьютере A, сеанс можно сохранить только на одном из серверов. В настоящее время вы не могу получить доступ к машинам B, C, потому что сессия не хранится на B и C, и легитимность запроса пользователя можно проверить с помощью токена, и я могу добавить еще несколько машин, так что масштабируемость хорошая.
  3. Зная из 2, это поддерживает междоменный доступ.

Spring Boot

Spring Boot — это платформа для упрощения процесса создания и разработки приложений Spring. После его использования вы будете кричать: "wocao! Как может быть такая удобная вещь! Мама больше не должна беспокоиться о том, что я не настрою файл конфигурации xml!".

Spring Security

Это структура управления разрешениями безопасности, предоставляемая Spring Security, которая может настраивать соответствующие ролевые удостоверения и разрешения удостоверений в соответствии с потребностями пользователей, выполнять операции с черным списком и перехватывать несанкционированные операции. С помощью Spring Boot можно быстро разработать полную систему разрешений.

Процесс выполнения Spring Security в этом техническом решении

本次技术方案中 Spring Security 执行流程
Процесс выполнения Spring Security в этом техническом решении

Из рисунка видно, что этот процесс выполнения вращается вокругtoken.

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

Практическая реализация этой системы безопасности

Шаг 1. Создайте проект и настройте источник данных

  1. Создайте проект Spring Boot с Itellij Idea

需要的组件
необходимые компоненты

Выберите четыре компонента Web, Security, Mybatis и JDBC.

  1. Устанавливаем нужную базу данных в базу данных spring_security

建立数据库
построить базу данных

  1. Настройте источник данных в файле конфигурации весенней загрузки application.properties.

配置数据源
Настроить источник данных

  1. Запустите проект, чтобы увидеть, настроил ли Spring Boot для нас Spring Security.

启动项目
Стартовый проект

Если он запускается правильно, вы можете видеть, что Spring Security сгенерировал пароль по умолчанию.

мы посетилиlocalhost:8080Появится окно базовой аутентификации.

Введите имя пользователяuserпароль前面自动生成的密码Вы можете получить переданное сообщение возврата (возврат 404, потому что мы еще не создали ни одной страницы)
Ввод неверного имени пользователя или пароля вернет 401 , что означает, что он не аутентифицирован.

Если вы дошли до этого момента, значит, вы настроили необходимое окружение, а затем переходите к следующему шагу!

Шаг 2: Сгенерируйте наш jwt

На этом этапе мы узнаем, как создать собственный токен в соответствии с нашими потребностями!

  1. Отключите Spring Security, который Spring Boot настроил для нас. (Поскольку конфигурация Spring Security по умолчанию перехватит наш настроенный интерфейс входа в систему)

Создайте класс конфигурации Spring SecurityWebSecurityConfig.java

@Configuration      // 声明为配置类
@EnableWebSecurity      // 启用 Spring Security web 安全的功能
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().permitAll()       // 允许所有请求通过
                .and()
                .csrf()
                .disable()                      // 禁用 Spring Security 自带的跨域处理
                .sessionManagement()                        // 定制我们自己的 session 策略
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 调整为让 Spring Security 不创建和使用 session
    }
}
  1. Создайте соответствующих пользователей и роли в базе данных.

Создать пользовательскую таблицуuser

user 表
пользовательская таблица

Свойства и функции следующие:

  • имя пользователя: имя пользователя
  • пароль : пароль
  • role_id : идентификатор роли, к которой принадлежит пользователь
  • last_password_change : время последней смены пароля
  • enable : включить ли учетную запись, ее можно использовать для внесения в черный список

Создайте таблицу ролейrole

role 表
таблица ролей

Функции каждого атрибута следующие:

  • role_id : соответствующий идентификатор роли
  • role_name : имя роли
  • auth : разрешения, которые есть у роли
  1. Напишите соответствующую логику определения пароля для входа в систему.

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

  1. Напишите класс операции с токеном (создайте часть токена)

Поскольку в Интернете есть встроенные колеса, мы можем использовать их сразу после внесения некоторых изменений.

Используйте maven для импорта колеса jwt, сделанного в Интернете.

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.4</version>
</dependency>

Создайте наш собственный класс действия токенаTokenUtils.java

public class TokenUtils {

    private final Logger logger = Logger.getLogger(this.getClass());

    @Value("${token.secret}")
    private String secret;

    @Value("${token.expiration}")
    private Long expiration;

    /**
     * 根据 TokenDetail 生成 Token
     *
     * @param tokenDetail
     * @return
     */
    public String generateToken(TokenDetail tokenDetail) {
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("sub", tokenDetail.getUsername());
        claims.put("created", this.generateCurrentDate());
        return this.generateToken(claims);
    }

    /**
     * 根据 claims 生成 Token
     *
     * @param claims
     * @return
     */
    private String generateToken(Map<String, Object> claims) {
        try {
            return Jwts.builder()
                    .setClaims(claims)
                    .setExpiration(this.generateExpirationDate())
                    .signWith(SignatureAlgorithm.HS512, this.secret.getBytes("UTF-8"))
                    .compact();
        } catch (UnsupportedEncodingException ex) {
            //didn't want to have this method throw the exception, would rather log it and sign the token like it was before
            logger.warn(ex.getMessage());
            return Jwts.builder()
                    .setClaims(claims)
                    .setExpiration(this.generateExpirationDate())
                    .signWith(SignatureAlgorithm.HS512, this.secret)
                    .compact();
        }
    }

    /**
     * token 过期时间
     *
     * @return
     */
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + this.expiration * 1000);
    }

    /**
     * 获得当前时间
     *
     * @return
     */
    private Date generateCurrentDate() {
        return new Date(System.currentTimeMillis());
    }

}

В настоящее время этот служебный класс делает следующее:

  • Тело токена, которое инкапсулирует имя пользователя в загруженное колесо.claims, и инкапсулирует в него текущее время (удобно потом судить, был ли сгенерирован токен до смены пароля)
  • Затем рассчитайте время, когда истечет срок действия токена, и запишите его в токен колеса.
  • Посолите и зашифруйте токен колеса, чтобы сгенерировать строку строк, которая является нашим пользовательским токеном.

Входные параметры метода для генерации пользовательского токенаTokenDetailопределяется следующим образом

public interface TokenDetail {

    //TODO: 这里封装了一层,不直接使用 username 做参数的原因是可以方便未来增加其他要封装到 token 中的信息

    String getUsername();
}

public class TokenDetailImpl implements TokenDetail {

    private final String username;

    public TokenDetailImpl(String username) {
        this.username = username;
    }

    @Override
    public String getUsername() {
        return this.username;
    }
}

В то же время этот класс инструментов извлекает зашифрованную строку токена и время истечения срока действия токена в application.properties.

# token 加密密钥
token.secret=secret
# token 过期时间,以秒为单位,604800 是 一星期
token.expiration=604800
  1. На данный момент наше руководство по созданию токена завершено.Что касается интерфейса входа в систему, операция по оценке правильности пароля учетной записи остается на усмотрение читателя.Читателю нужно только вернуть сгенерированный токен пользователю в результате, когда вход прошел успешно.

Шаг 3: Реализуйте функцию проверки действительности токена и получения сведений об учетной записи (разрешение, находится ли учетная запись в заблокированном состоянии) в соответствии с токеном.

  1. Анализ процесса внедрения

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

Предположим, мы получаем строку токенов, переданных пользователем в это время, и чтобы получить сведения о пользователе на основе этой строки токенов, мы можем сделать это:

A. Попробуйте разобрать эту строку токенов, если она успешно разобрана, перейти к следующему шагу, в противном случае завершить процесс разбора
B. Найдите учетную запись пользователя в базе данных по проанализированному имени пользователя, времени последней модификации пароля, разрешениям, заблокирована ли учетная запись или нет, и инкапсулируйте информацию в класс сущности (класс userDetail). Если пользователь не может быть найден, прекратите процесс синтаксического анализа
C. Проверьте статус бана, записанный в userDetail.Если учетная запись была забанена, верните результат бана и прекратите запрос.
D. Сравните, находится ли токен в пределах срока действия в соответствии с userDtail, если он не в пределах срока действия, прекратите процесс синтаксического анализа, в противном случае продолжите
E. Запишите разрешения пользователя, записанные в userDetail, в этот сеанс запроса, и синтаксический анализ будет завершен.

Пожалуйста, обратитесь к следующему рисунку, чтобы понять:

分析解析 token ,检查权限的过程
Процесс анализа и парсинга токенов и проверки разрешений

Давайте начнем

  1. попробуйте разобрать токен, чтобы получить имя пользователя
/**
     * 从 token 中拿到 username
     *
     * @param token
     * @return
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = this.getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 解析 token 的主体 Claims
     *
     * @param token
     * @return
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(this.secret.getBytes("UTF-8"))
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

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

  1. Получить информацию о пользователе из базы данных userDetail

Здесь мы реализуем интерфейс UserDetailService Spring Security, который имеет только один метод loadUserByUsername. Блок-схема выглядит следующим образом

获得 UserDetail 的流程图
Блок-схема для получения UserDetail

код показывает, как показано ниже:

public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 获取 userDetail
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user = this.userMapper.getUserFromDatabase(username);

        if (user == null) {
            throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
        } else {
            return SecurityModelFactory.create(user);
        }
    }
}

public class User implements LoginDetail, TokenDetail {

    private String username;
    private String password;
    private String authorities;
    private Long lastPasswordChange;
    private char enable;

    // 省略构造器和 getter setter 方法
}

public class SecurityModelFactory {

    public static UserDetailImpl create(User user) {
        Collection<? extends GrantedAuthority> authorities;
        try {
            authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getAuthorities());
        } catch (Exception e) {
            authorities = null;
        }

        Date lastPasswordReset = new Date();
        lastPasswordReset.setTime(user.getLastPasswordChange());
        return new UserDetailImpl(
                user.getUsername(),
                user.getUsername(),
                user.getPassword(),
                lastPasswordReset,
                authorities,
                user.enable()
        );
    }

}

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

public interface UserMapper {


    User getUserFromDatabase(@Param("username") String username);

}

Соответствующий XML-файл:

<select id="getUserFromDatabase"  resultMap="getUserFromDatabaseMap">
        SELECT
        `user`.username,
        `user`.`password`,
        `user`.role_id,
        `user`.enable,
        `user`.last_password_change,
        `user`.enable,
        role.auth
        FROM
        `user` ,
        role
        WHERE
        `user`.role_id = role.role_id AND
        `user`.username = #{username}
    </select>

    <resultMap id="getUserFromDatabaseMap" type="cn.ssd.wean2016.springsecurity.model.domain.User">
        <id column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="last_password_change" property="lastPasswordChange"/>
        <result column="auth" property="authorities"/>
        <result column="enable" property="enable"/>
    </resultMap>

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

Шаг 4: Определите перехватчик, который анализирует токен

Старые правила, блок-схема выше:

解析 token 的拦截器的执行流程
Поток выполнения перехватчика, который анализирует токен

Определите этот перехватчик ниже

public class AuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {

    /**
     * json web token 在请求头的名字
     */
    @Value("${token.header}")
    private String tokenHeader;

    /**
     * 辅助操作 token 的工具类
     */
    @Autowired
    private TokenUtils tokenUtils;

    /**
     * Spring Security 的核心操作服务类
     * 在当前类中将使用 UserDetailsService 来获取 userDetails 对象
     */
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 将 ServletRequest 转换为 HttpServletRequest 才能拿到请求头中的 token
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        // 尝试获取请求头的 token
        String authToken = httpRequest.getHeader(this.tokenHeader);
        // 尝试拿 token 中的 username
        // 若是没有 token 或者拿 username 时出现异常,那么 username 为 null
        String username = this.tokenUtils.getUsernameFromToken(authToken);

        // 如果上面解析 token 成功并且拿到了 username 并且本次会话的权限还未被写入
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            // 用 UserDetailsService 从数据库中拿到用户的 UserDetails 类
            // UserDetails 类是 Spring Security 用于保存用户权限的实体类
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            // 检查用户带来的 token 是否有效
            // 包括 token 和 userDetails 中用户名是否一样, token 是否过期, token 生成时间是否在最后一次密码修改时间之前
            // 若是检查通过
            if (this.tokenUtils.validateToken(authToken, userDetails)) {
                // 生成通过认证
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
                // 将权限写入本次会话
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
            if (!userDetails.isEnabled()){
                response.setCharacterEncoding("UTF-8");
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().print("{\"code\":\"452\",\"data\":\"\",\"message\":\"账号处于黑名单\"}");
                return;
            }
        }
        chain.doFilter(request, response);
    }

}

который проверяет, действителен ли токенtokenUtils.validateToken(authToken, userDetails)Метод определяется следующим образом:

/**
     * 检查 token 是否处于有效期内
     * @param token
     * @param userDetails
     * @return
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        UserDetailImpl user = (UserDetailImpl) userDetails;
        final String username = this.getUsernameFromToken(token);
        final Date created = this.getCreatedDateFromToken(token);
        return (username.equals(user.getUsername()) && !(this.isTokenExpired(token)) && !(this.isCreatedBeforeLastPasswordReset(created, user.getLastPasswordReset())));
    }

    /**
     * 获得我们封装在 token 中的 token 创建时间
     * @param token
     * @return
     */
    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = this.getClaimsFromToken(token);
            created = new Date((Long) claims.get("created"));
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    /**
     * 获得我们封装在 token 中的 token 过期时间
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = this.getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    /**
     * 检查当前时间是否在封装在 token 中的过期时间之后,若是,则判定为 token 过期
     * @param token
     * @return
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = this.getExpirationDateFromToken(token);
        return expiration.before(this.generateCurrentDate());
    }

    /**
     * 检查 token 是否是在最后一次修改密码之前创建的(账号修改密码之后之前生成的 token 即使没过期也判断为无效)
     * @param created
     * @param lastPasswordReset
     * @return
     */
    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

Шаг 5: Зарегистрируйте перехватчик на шаге 4, чтобы он записывал разрешения пользователя в сеанс до того, как Spring Security прочитает разрешения сеанса.

Класс конфигурации в SpringSecurityWebSecurityConfig.javaДобавьте следующую конфигурацию в

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 注册 token 转换拦截器为 bean
     * 如果客户端传来了 token ,那么通过拦截器解析 token 赋予用户权限
     *
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
        AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
        authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
        return authenticationTokenFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/auth").authenticated()       // 需携带有效 token
                .antMatchers("/admin").hasAuthority("admin")   // 需拥有 admin 这个权限
                .antMatchers("/ADMIN").hasRole("ADMIN")     // 需拥有 ADMIN 这个身份
                .anyRequest().permitAll()       // 允许所有请求通过
                .and()
                .csrf()
                .disable()                      // 禁用 Spring Security 自带的跨域处理
                .sessionManagement()                        // 定制我们自己的 session 策略
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 调整为让 Spring Security 不创建和使用 session


        /**
         * 本次 json web token 权限控制的核心配置部分
         * 在 Spring Security 开始判断本次会话是否有权限时的前一瞬间
         * 通过添加过滤器将 token 解析,将用户所有的权限写入本次 Spring Security 的会话
         */
        http
                .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
    }
}

Среди них мы регистрируем перехватчик, определенный на шаге 4, в Spring как bean-компонент и регистрируем его в момент, прежде чем Spring Security запустится, чтобы определить, есть ли у сеанса разрешение, проанализируем токен, добавив фильтр, и запишем все разрешения пользователя в этот сеанс.

Во-вторых, мы добавляем три правила блокировки адресов в стиле муравья:

  • /auth : требуется действительный токен
  • /admin : учетная запись, соответствующая токену, должна иметь права администратора.
  • /ADMIN : требует, чтобы учетная запись, соответствующая токену, имела идентификатор ROLE_ADMIN.

Запустите программу на порт 8080 и войдите в гостевую учетную запись через интерфейс /login./authИнтерфейс пытается получить доступ, результаты следующие:

对 auth 的访问结果
Результат доступа к авторизации

Очевидно, поскольку токен действителен, перехват успешно пройден.

Далее попытайтесь получить доступ/adminинтерфейс, результат такой:

对 admin 的访问结果
Результат доступа к админке

Очевидно, поскольку переносимый токен не имеет прав администратора, запрос перехватывается и перехватывается

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

Шаг 6. Завершите возврат результатов 401 и 403.

Определить обработчик 401, внедритьAuthenticationEntryPointинтерфейс

public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {

  /**
   * 未登录或无权限时触发的操作
   * 返回  {"code":401,"message":"小弟弟,你没有携带 token 或者 token 无效!","data":""}
   * @param httpServletRequest
   * @param httpServletResponse
   * @param e
   * @throws IOException
   * @throws ServletException
   */
  @Override
  public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
      //返回json形式的错误信息
      httpServletResponse.setCharacterEncoding("UTF-8");
      httpServletResponse.setContentType("application/json");

      httpServletResponse.getWriter().println("{\"code\":401,\"message\":\"小弟弟,你没有携带 token 或者 token 无效!\",\"data\":\"\"}");
      httpServletResponse.getWriter().flush();
  }

}

Определить обработчик 403, внедритьAccessDeniedHandlerинтерфейс

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        //返回json形式的错误信息
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json");

        httpServletResponse.getWriter().println("{\"code\":403,\"message\":\"小弟弟,你没有权限访问呀!\",\"data\":\"\"}");
        httpServletResponse.getWriter().flush();
    }
}

Настройте эти два процессора в классе конфигурации SpringSecurity:

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 注册 401 处理器
     */
    @Autowired
    private EntryPointUnauthorizedHandler unauthorizedHandler;

    /**
     * 注册 403 处理器
     */
    @Autowired
    private MyAccessDeniedHandler accessDeniedHandler;

    ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                ...

                // 配置被拦截时的处理
                .exceptionHandling()
                .authenticationEntryPoint(this.unauthorizedHandler)   // 添加 token 无效或者没有携带 token 时的处理
                .accessDeniedHandler(this.accessDeniedHandler)      //添加无权限时的处理

                ...
    }
}

Попытка доступа к интерфейсу /admin в качестве гостя, результат следующий:

以 guest 身份访问 admin 接口
Доступ к интерфейсу администратора в качестве гостя

Хи-хи, очевидно, миссия выполнена! ! ! (Этот интерфейс также может быть настроен с помощью лямбда-выражений, это может изучить каждый~~~)

ускользнуть...

Категории