Система разрешений на основе Spring Security и JWT

Java
Система разрешений на основе Spring Security и JWT

1. Сценарий

Front-end и back-end разделены, как обеспечить безопасность взаимодействия данных?

Кто-то: жетон

Такие клиенты, как мобильные терминалы, небольшие программы и H5, также передаются через данные JSON, что также включает проверку личности.

Во-вторых, проблема

Как реализовать Токен, это лучший способ?

Опыт работы в сотнях проектов (.(▼へ▼メ) претенциозный.) Я видел много проектов, реализованных таким образом

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

3. Решения

Сегодня мы объясним использованиеJWT, чтобы решить проблему с токеном

#Наука о JWT

JSON Web Token (JWT)Открытый стандарт на основе JSON для передачи информации между веб-приложениями.(RFC 7519), используемый для безопасной передачи информации между различными системами в виде объектов JSON. Основной сценарий использования обычно используется для передачи аутентифицированной информации об удостоверении пользователя между поставщиками удостоверений и поставщиками услуг.

JWT состоит из трех частей, а именно:

  • ЗаголовокСодержит две части: тип токена и используемый алгоритм подписи.
  • Полезная нагрузкаЭто определяемый пользователем контент, переносимый токеном.Данные представлены в формате base64, не зашифрованы, могут быть взломаны и не могут хранить конфиденциальную информацию; чем больше контента, тем длиннее токен.
  • ПодписьОбъедините Header+Payload в строку и вычислите ее с помощью указанного алгоритма подписи, чтобы получить значение подписи.Сервер может использовать этот флаг, чтобы определить, является ли токен законным или нет.
# Объясните безопасность Spring (бонус)

Для решения проблемы с токеном есть JWT.В сочетании с Spring Security он может решить проблему модулей разрешений пользователей (действительно очень мощных).Конечно, помимо Spring Security, Apache Shiro также может решать разрешения, но это не так мощный, как безопасность.

В-четвертых, метод реализации

Внедрить банку зависимостей JWT и Spring Security в pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

Класс инструмента JWT, создание токена, проверка токена, обновление токена и т. д.

@Component
public class JwtTokenUtil {

    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    
    // 生成token
    public String generateToken(UserDetails userDetails) {
        ...
    }
    
    // 刷新token
    public String refreshToken(String token) {
        ...
    }
    
    // 校验token
    public Boolean validateToken(String token) {
        ...
    }
    }

Фильтр токенов, который обрабатывает токен интерфейса.

@Component
public class JwtTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader(Constant.HEADER_STRING );
        if (authHeader != null && authHeader.startsWith(Constant.TOKEN_PREFIX )) {
            final String authToken = authHeader.substring(Constant.TOKEN_PREFIX.length() );
            String username = jwtTokenUtil.getUsernameFromToken(authToken);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
                            request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

Основная конфигурация Spring Security

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userService;

    @Bean
    public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
        return new JwtTokenFilter();
    }

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        String[] urls = new String[]{"/user/login", "/user/register"};
        httpSecurity.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                // 允许注册和登录接口不需token访问
                .antMatchers(urls).permitAll()
                .anyRequest().authenticated();
        httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        httpSecurity.headers().cacheControl();
    }
}

Проверка разрешений, если интерфейсу необходимо ограничивать ограничения доступа, добавьте @PreAuthorize

@RestController
public class RoleController {

    /**
     * 测试普通权限
     *
     * @return
     */
    @PreAuthorize("hasAuthority('ROLE_NORMAL')")
    @GetMapping(value="/normal/test")
    public String test1() {
        return "普通角色访问";
    }

    /**
     * 测试管理员权限
     *
     * @return
     */
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @GetMapping(value = "/admin/test")
    public String test2() {
        return "管理员访问";
    }
}

Вам необходимо добавить роль пользователю для доступа, как показано ниже: Таблица взаимосвязей ролей пользователей

用户角色关系表

5. Как проверить токен

Примечание. JWT по умолчанию не зашифрован, его может прочитать любой, вы можете скопировать строку токенов вОнлайн расшифровка JWT

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJPd2F0ZXIiLCJjcmVhdGVkIjoxNTYzMzc2ODE2NTIzLCJleHAiOjE1NjMzODQwMTZ9.SuL4dRoweriLOnZzohcEzUwXf7kVSx9KnTIGtB7ffuBtlUUFS1T8il7_fxv3Gn1LkX5DGOawqNhG4ZWYxALDig

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

Тогда токен не будет подделан? Нет, потому что в трех частях токена (заголовок, полезная нагрузка, подпись) можно предотвратить подделку подписи, этот ключ известен только серверу.

6. Тестовый эффект

  • Вернуть токен после успешного входа

  • Без токена доступ будет запрещен

  • правильный способ доступа

Исходный код здесь