Spring Boot Security интегрирует службу интерфейса API безопасности проектирования OAuth2.

Spring Boot

Введение

OAuth является открытым сетевым стандартом для авторизации и широко используется во всем мире.Текущая версия – версия 2.0. В этой статье основное внимание уделяется реализации OAuth2 в проекте Spring Boot.Если вы мало знаете об OAuth2, вы можете сначала понять его.OAuth 2.0 — Жуань Ифэн, это хорошая научно-популярная статья по oauth2.

Обзор OAuth2

oauth2 разделен на 4 режима в соответствии с различными сценариями использования.

  • Код авторизации
  • Упрощенный режим (неявный)
  • Режим пароля (учетные данные пароля владельца ресурса)
  • Режим клиента (учетные данные клиента)

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

Авторизация Oauth2 в основном состоит из двух частей:

  • Сервер авторизации: Служба аутентификации
  • Сервер ресурсов: служба ресурсов

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

Начать быстро

В предыдущей статье объяснялась безопасность Spring, в этом разделе подробно не объясняется конфигурация с использованием Spring Security. Если вы не понимаете Spring Security, перейдите кПодробное объяснение Spring Boot Security.

построить таблицу

Информация о клиенте может храниться в памяти, Redis и базе данных. Redis и хранилище баз данных обычно используются в реальных проектах. В этой статье используется база данных. Spring 0Auth2 разработал таблицу базы данных, и она неизменяема. Описание таблиц и полей относится к:Таблица базы данных Oauth2 показывает.

Скрипт для создания базы данных 0Auth2 выглядит следующим образом:

DROP TABLE IF EXISTS `clientdetails`;
DROP TABLE IF EXISTS `oauth_access_token`;
DROP TABLE IF EXISTS `oauth_approvals`;
DROP TABLE IF EXISTS `oauth_client_details`;
DROP TABLE IF EXISTS `oauth_client_token`;
DROP TABLE IF EXISTS `oauth_refresh_token`;

CREATE TABLE `clientdetails` (
  `appId` varchar(128) NOT NULL,
  `resourceIds` varchar(256) DEFAULT NULL,
  `appSecret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `grantTypes` varchar(256) DEFAULT NULL,
  `redirectUrl` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additionalInformation` varchar(4096) DEFAULT NULL,
  `autoApproveScopes` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_approvals` (
  `userId` varchar(256) DEFAULT NULL,
  `clientId` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` datetime DEFAULT NULL,
  `lastModifiedAt` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(128) NOT NULL,
  `resource_ids` varchar(256) DEFAULT NULL,
  `client_secret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `authorized_grant_types` varchar(256) DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) DEFAULT NULL,
  `autoapprove` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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

INSERT INTO `oauth_client_details` VALUES ('dev', '', 'dev', 'app', 'password,client_credentials,authorization_code,refresh_token', 'http://www.baidu.com', '', 3600, 3600, '{\"country\":\"CN\",\"country_code\":\"086\"}', 'false');

Таблицы, используемые для пользователей, разрешений и ролей, следующие:

DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `role`;
DROP TABLE IF EXISTS `user_role`;
DROP TABLE IF EXISTS `role_permission`;
DROP TABLE IF EXISTS `permission`;

CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`) 
);
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`) 
);
CREATE TABLE `user_role` (
`user_id` bigint(11) NOT NULL,
`role_id` bigint(11) NOT NULL
);
CREATE TABLE `role_permission` (
`role_id` bigint(11) NOT NULL,
`permission_id` bigint(11) NOT NULL
);
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) NULL,
`pid` bigint(11) NOT NULL,
PRIMARY KEY (`id`) 
);

INSERT INTO user (id, username, password) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e'); 
INSERT INTO user (id, username , password) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e'); 
INSERT INTO role (id, name) VALUES (1,'USER');
INSERT INTO role (id, name) VALUES (2,'ADMIN');
INSERT INTO permission (id, url, name, pid) VALUES (1,'/**','',0);
INSERT INTO permission (id, url, name, pid) VALUES (2,'/**','',0);
INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 2);
INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);

Структура проекта

resources
|____templates
| |____login.html
| |____application.yml
java
|____com
| |____gf
| | |____SpringbootSecurityApplication.java
| | |____config
| | | |____SecurityConfig.java
| | | |____MyFilterSecurityInterceptor.java
| | | |____MyInvocationSecurityMetadataSourceService.java
| | | |____ResourceServerConfig.java
| | | |____WebResponseExceptionTranslateConfig.java
| | | |____AuthorizationServerConfiguration.java
| | | |____MyAccessDecisionManager.java
| | |____entity
| | | |____User.java
| | | |____RolePermisson.java
| | | |____Role.java
| | |____mapper
| | | |____PermissionMapper.java
| | | |____UserMapper.java
| | | |____RoleMapper.java
| | |____controller
| | | |____HelloController.java
| | | |____MainController.java
| | |____service
| | | |____MyUserDetailsService.java

ключевой код

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

SecurityConfig

Поддержка режима пароля для настройки аутентификации

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService userService;


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //校验用户
        auth.userDetailsService( userService ).passwordEncoder( new PasswordEncoder() {
            //对密码进行加密
            @Override
            public String encode(CharSequence charSequence) {
                System.out.println(charSequence.toString());
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }
            //对密码进行判断匹配
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                boolean res = s.equals( encode );
                return res;
            }
        } );

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.requestMatchers()
                .antMatchers("/oauth/**","/login","/login-error")
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**").authenticated()
                .and()
                .formLogin().loginPage( "/login" ).failureUrl( "/login-error" );
    }


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

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return Objects.equals(charSequence.toString(),s);
            }
        };
    }


}

Конфигурация сервера аутентификации AuthorizationServerConfiguration

/**
 * 认证服务器配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {


    /**
     * 注入权限验证控制器 来支持 password grant type
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 注入userDetailsService,开启refresh_token需要用到
     */
    @Autowired
    private MyUserDetailsService userDetailsService;

    /**
     * 数据源
     */
    @Autowired
    private DataSource dataSource;

    /**
     * 设置保存token的方式,一共有五种,这里采用数据库的方式
     */
    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private WebResponseExceptionTranslator webResponseExceptionTranslator;

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore( dataSource );
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //开启密码授权类型
        endpoints.authenticationManager(authenticationManager);
        //配置token存储方式
        endpoints.tokenStore(tokenStore);
        //自定义登录或者鉴权失败时的返回信息
        endpoints.exceptionTranslator(webResponseExceptionTranslator);
        //要使用refresh_token的话,需要额外配置userDetailsService
        endpoints.userDetailsService( userDetailsService );

    }


}

ResourceServerConfig Конфигурация сервера ресурсов

/**
 * 资源提供端的配置
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    /**
     * 这里设置需要token验证的url
     * 可以在WebSecurityConfigurerAdapter中排除掉,
     * 对于相同的url,如果二者都配置了验证
     * 则优先进入ResourceServerConfigurerAdapter,进行token验证。而不会进行
     * WebSecurityConfigurerAdapter 的 basic auth或表单认证。
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers("/hi")
                .and()
                .authorizeRequests()
                .antMatchers("/hi").authenticated();
    }


}

Фильтр MyFilterSecurityInterceptor

@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {


    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
        super.setAccessDecisionManager(myAccessDecisionManager);
    }


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Allow-Headers", ":x-requested-with,content-type");
        filterChain.doFilter(servletRequest,servletResponse);
        if (!request.getRequestURI().equals("/oauth/token")) {
            invoke(fi);
        }

    }

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            //执行下一个拦截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {

        return this.securityMetadataSource;
    }


}

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

проверять

Режим авторизации по паролю

[Режим пароля требует параметров: имя пользователя, пароль, тип_гранта, идентификатор_клиента, секрет_клиента]

токен запроса

curl -X POST -d "username=admin&password=123456&grant_type=password&client_id=dev&client_secret=dev" http://localhost:8080/oauth/token

возвращение

{
	"access_token": "d94ec0aa-47ee-4578-b4a0-8cf47f0e8639",
	"token_type": "bearer",
	"refresh_token": "23503bc7-4494-4795-a047-98db75053374",
	"expires_in": 3475,
	"scope": "app"
}

Доступ к ресурсам без токена,

curl http://localhost:8080/hi\?name\=zhangsan

Несанкционированная подсказка возврата

{
	"error": "unauthorized",
	"error_description": "Full authentication is required to access this resource"
}

Ношение токена для доступа к ресурсам

curl http://localhost:8080/hi\?name\=zhangsan\&access_token\=164471f7-6fc6-4890-b5d2-eb43bda3328a

вернуть правильный

hi , zhangsan

обновить токен

curl  -X POST -d 'grant_type=refresh_token&refresh_token=23503bc7-4494-4795-a047-98db75053374&client_id=dev&client_secret=dev' http://localhost:8080/oauth/token

возвращение

{
    "access_token": "ef53eb01-eb9b-46d8-bd58-7a0f9f44e30b",
    "token_type": "bearer",
    "refresh_token": "23503bc7-4494-4795-a047-98db75053374",
    "expires_in": 3599,
    "scope": "app"
}

Режим авторизации клиента

[Клиентский режим требует параметров: grant_type, client_id, client_secret]

токен запроса

curl -X POST -d "grant_type=client_credentials&client_id=dev&client_secret=dev" http://localhost:8080/oauth/token

возвращение

{
	"access_token": "a7be47b3-9dc8-473e-967a-c7267682dc66",
	"token_type": "bearer",
	"expires_in": 3564,
	"scope": "app"
}

Режим кода авторизации

получить код

Посетите следующий адрес в вашем браузере:

http://localhost:8080/oauth/authorize?response_type=code&client_id=dev&redirect_uri=http://www.baidu.com

Перейдите на страницу входа, введите учетную запись и пароль для аутентификации:

После аутентификации он перепрыгивает на страницу подтверждения авторизации (когда поле «Автоприимчатая» в таблице oouth_client_details установлено значение true, страница подтверждения авторизации не появится):

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

Изменить токен по коду

curl -X POST -d "grant_type=authorization_code&code=qS03iu&client_id=dev&client_secret=dev&redirect_uri=http://www.baidu.com" http://localhost:8080/oauth/token

возвращение

{
    "access_token": "90a246fa-a9ee-4117-8401-ca9c869c5be9",
    "token_type": "bearer",
    "refresh_token": "23503bc7-4494-4795-a047-98db75053374",
    "expires_in": 3319,
    "scope": "app"
}

Ссылаться на

сегмент fault.com/ah/119000001…

stackoverflow.com/questions/2…

исходный код

GitHub.com/Kung Fu-Change сенсорный экран…