SpringCloud: OAuth2 и JWT

Java

Создайте сервер oauth2, включая серверы аутентификации, авторизации и ресурсов.

использованная литература:

блог woo woo woo.cn на.com/invoice2952/afraid/89…

nuggets.capable/post/684490…

Официальная документация Spring OAuth2

Эта статья разделена на две части

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

адрес проекта:GitHub.com/like this/spr…

ветка oauth

1. Упрощенная версия

Используйте Spring Initializr для создания нового проекта и проверьте следующие три параметра.

file

pom.xml

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        //只需要引用这一个 
     //集成了spring-security-oauth2 spring-security-jwt spring-security-oauth2-autoconfigure
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

Настроить весеннюю безопасность

Новый класс WebSecurityConfig наследует WebSecurityConfigurerAdapter и добавляет аннотацию @Configuration @EnableWebSecurity для перезаписи трех методов. Код выглядит следующим образом, а подробности объясняются ниже кода.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserServiceDetail userServiceDetail;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userServiceDetail).passwordEncoder(passwordEncoder());
        //内存存储
//        auth
//                .inMemoryAuthentication()
//                .passwordEncoder(passwordEncoder())
//                .withUser("user")
//                .password(passwordEncoder().encode("user"))
//                .roles("USER");

    }


    /**
     * 配置了默认表单登陆以及禁用了 csrf 功能,并开启了httpBasic 认证
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http    // 配置登陆页/login并允许访问
                .formLogin().permitAll()
                // 登出页
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
                // 其余所有请求全部需要鉴权认证
                .and().authorizeRequests().anyRequest().authenticated()
                // 由于使用的是JWT,我们这里不需要csrf
                .and().csrf().disable();
    }

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

В основном объяснить

protected void configure(AuthenticationManagerBuilder auth) throws Exception

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

UserServiceDetail

ДостигнутоUserDetailsServiceинтерфейс, поэтому вам нужно реализовать единственный метод

package zcs.oauthserver.service;

import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import zcs.oauthserver.model.UserModel;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserServiceDetail implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE"));
        return new UserModel("user","user",authorities);
    }
}

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

Параметр s — это имя пользователя, введенное во внешнем интерфейсе, через которого осуществляется поиск в базе данных, получаются пароль и права доступа, и, наконец, эти три данных инкапсулируются вUserDetailsВозвращается в реализующем классе интерфейса. Инкапсулированные здесь классы можно использоватьorg.springframework.security.core.userdetails.Userили реализовать самостоятельноUserDetailsинтерфейс.

UserModel

выполнитьUserDetailsинтерфейс

package zcs.oauthserver.model;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.util.Collection;
import java.util.List;

public class UserModel implements UserDetails {
    private String userName;

    private String password;

    private List<SimpleGrantedAuthority> authorities;

    public UserModel(String userName, String password, List<SimpleGrantedAuthority> authorities) {
        this.userName = userName;
        this.password = new BCryptPasswordEncoder().encode(password);;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return userName;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

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

Настройка сервера аутентификации Oauth2

Новый класс конфигурации AuthorizationServerConfig наследует AuthorizationServerConfigurerAdapter и добавляет @Configuration Аннотация @EnableAuthorizationServer указывает на сервер аутентификации.

Переопределить три функции

  • ClientDetailsServiceConfigurer: используется для настройки службы сведений о клиенте, здесь инициализируется информация о клиенте, вы можете записать здесь сведения о клиенте или сохранить и получить данные через базу данных. Клиент обращается к стороннему приложению
  • AuthorizationServerSecurityConfigurer: ограничения безопасности, используемые для настройки конечной точки токена.
  • AuthorizationServerEndpointsConfigurer: доступ к конечным точкам и службам токенов для настройки авторизации и токенов.
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
	//从WebSecurityConfig加载
    @Autowired
    private AuthenticationManager authenticationManager;
    //内存存储令牌
    private TokenStore tokenStore = new InMemoryTokenStore();

    /**
     * 配置客户端详细信息
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            	//客户端ID
                .withClient("zcs")
                .secret(new BCryptPasswordEncoder().encode("zcs"))
                //权限范围
                .scopes("app")
            	//授权码模式
                .authorizedGrantTypes("authorization_code")
                //随便写
                .redirectUris("www.baidu.com");
//        clients.withClientDetails(new JdbcClientDetailsService(dataSource));
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore)
                .authenticationManager(authenticationManager);
    }

    /**
     * 在令牌端点定义安全约束
     * 允许表单验证,浏览器直接发送post请求即可获取tocken
     * 这部分写这样就行
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 开启/oauth/token_key验证端口无权限访问
                .tokenKeyAccess("permitAll()")
                // 开启/oauth/check_token验证端口认证权限访问
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }
}

Данные клиента также используются для тестирования, а база данных будет добавлена ​​позже. Служба токенов временно хранится в памяти, а jwt будет добавлен позже.

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

Настроить сервер ресурсов

Сервер ресурсов также является служебной программой, то есть сервером, к которому необходимо получить доступ.

Новый ResourceServerConfig наследует ResourceServerConfigurerAdapter.

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
//                antMatcher表示只能处理/user的请求
                .antMatcher("/user/**")
                .authorizeRequests()
                .antMatchers("/user/test1").permitAll()
                .antMatchers("/user/test2").authenticated()
//                .antMatchers("user/test2").hasRole("USER")
//                .anyRequest().authenticated()
        ;
    }
}

ResourceServerConfigurerAdapterЗначение по умолчанию Order равно 3, меньше чемWebSecurityConfigurerAdapter, чем меньше значение, тем выше приоритет

оResourceServerConfigurerAdapterиWebSecurityConfigurerAdapterПодробнее см.

woo woo Краткое описание.com/afraid/share 1194 руб 8…

Новый пользовательский контроллер

@RestController
public class UserController {
    @GetMapping("/user/me")
    public Principal user(Principal principal) {
        return principal;
    }

    @GetMapping("/user/test1")
    public String test() {
        return "test1";
    }

    @GetMapping("/user/test2")
    public String test2() {
        return "test2";
    }

}

контрольная работа

  1. получить код доступ через браузерhttp://127.0.0.1:9120/oauth/authorize?client_id=zcs&response_type=code&redirect_uri=www.baidu.com, а затем прыгнуть с целевой страницы,

file
Сертификация

file

В адресной строке появится страница обратного звонка с параметром codehttp://127.0.0.1:9120/oauth/www.baidu.com?code=FGQ1jg

  1. получить жетон доступ почтальонаhttp://127.0.0.1:9120/oauth/token?code=FGQ1jg&grant_type=authorization_code&redirect_uri=www.baidu.com&client_id=zcs&client_secret=zcs, код введите только что полученный код, используйте запрос POST
    file
  2. доступ к ресурсам /user/test2 — это защищенный ресурс, доступ к которому осуществляется через токен
    file

2. Обновленная версия

JWT

Многие будут сравнивать JWT и OAuth2, на самом деле это совершенно разные понятия и несопоставимы.

JWT — это протокол аутентификации, который предоставляет метод выдачи маркеров доступа и проверки выпущенных подписанных маркеров доступа.

OAuth2 — это структура авторизации, предоставляющая подробный механизм авторизации.

Spring Cloud OAuth2 интегрирует JWT для управления токенами, поэтому его удобно использовать.

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

Создайте сертификат jks с помощью инструмента jdk, войдите в bin каталога установки jdk через cmd и выполните команду

keytool -genkeypair -alias oauth2-keyalg RSA -keypass mypass -keystore oauth2.jks -storepass mypass

Файл oauth2.jks будет сгенерирован в текущем каталоге и помещен в каталог ресурсов.

Maven по умолчанию не загружает файлы в каталог ресурсов, поэтому его необходимо настроить в pom.xml и добавить при сборке.

	  <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
            </resource>
        </resources>
    </build>

Изменить часть кода в исходном AuthorizationServerConfig

	@Autowired
    private TokenStore tokenStore;	

	@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//        endpoints.tokenStore(tokenStore)
//                .authenticationManager(authenticationManager);
        endpoints.authenticationManager(authenticationManager)
                .accessTokenConverter(jwtAccessTokenConverter())
                .tokenStore(tokenStore);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 非对称加密算法对token进行签名
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        final JwtAccessTokenConverter converter = new CustomJwtAccessTokenConverter();
        // 导入证书
        KeyStoreKeyFactory keyStoreKeyFactory =
                new KeyStoreKeyFactory(new ClassPathResource("oauth2.jks"), "mypass".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("oauth2"));
        return converter;
    }

jwtAccessTokenConverterметод имеетCustomJwtAccessTokenConverterкласс, который наследуетJwtAccessTokenConverter, пользовательская добавленная дополнительная информация о токене

/**
 * 自定义添加额外token信息
 */
public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        DefaultOAuth2AccessToken defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(accessToken);
        Map<String, Object> additionalInfo = new HashMap<>();
        UserModel user = (UserModel)authentication.getPrincipal();
        additionalInfo.put("USER",user);
        defaultOAuth2AccessToken.setAdditionalInformation(additionalInfo);
        return super.enhance(defaultOAuth2AccessToken,authentication);
    }
}

Security

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

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

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

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

таблица user_role

Используйте mybatis-plus для генерации кода и преобразования предыдущегоUserServiceDetailиUserModel

UserServiceDetail

@Service
public class UserServiceDetail implements UserDetailsService {
    private final UserMapper userMapper;
    private final RoleMapper roleMapper;

    @Autowired
    public UserServiceDetail(UserMapper userMapper, RoleMapper roleMapper) {
        this.userMapper = userMapper;
        this.roleMapper = roleMapper;
    }

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("username", s);
        User user = userMapper.selectOne(userQueryWrapper);
        if (user == null) {
            throw new RuntimeException("用户名或密码错误");
        }

        user.setAuthorities(roleMapper.selectByUserId(user.getId()));
        return user;
    }
}

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

User

public class User implements Serializable, UserDetails {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @TableId(value = "username")
    private String username;

    @TableId(value = "password")
    private String password;

    @TableField(exist = false)
    private List<Role> authorities;

    public User() {
    }

    public Integer getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = new BCryptPasswordEncoder().encode(password);
    }

    public void setAuthorities(List<Role> authorities) {
        this.authorities = authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }


    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username=" + username +
                ", password=" + password +
                "}";
    }
}

объяснять:

Необходимо переписать метод в UserDetails для хранения разрешений пользователей.

@Override
public Collection<? extends GrantedAuthority> getAuthorities()

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

@TableField(exist = false)
private List<Role> authorities;

Реализуйте интерфейс GrantedAuthority для роли, нужно только имя органа

public class Role implements Serializable, GrantedAuthority {

    private static final long serialVersionUID = 1L;

    private String name;

    @Override
    public String toString() {
        return name;
    }

    @Override
    public String getAuthority() {
        return name;
    }
}

Добавьте новый метод в RoleMapper.java для запроса ролей, принадлежащих идентификатору пользователя.

   @Select("select name from role r INNER JOIN user_role ur on ur.user_id=1 and ur.role_id=r.id")
    List<Role> selectByUserId(Integer id);

контрольная работа

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

file

адрес проекта:GitHub.com/На этот раз Шерри, округ Колумбия/…

Ссылка на ссылку:

блог woo woo woo.cn на.com/invoice2952/afraid/89…

nuggets.capable/post/684490…Больше статей смотрите в личном блогеzheyday.github.io/