Securing REST API using custom tokens (stateless, no UI, no cookies, no basic authentication, no OAuth, no login page)

前端 未结 5 1869
轻奢々
轻奢々 2020-12-22 18:05

There are lots of guidelines, sample codes that show how to secure REST API with Spring Security, but most of them assume a web client and talk about login page, redirection

5条回答
  •  时光说笑
    2020-12-22 18:43

    The code secure all endpoints - but I'm sure that you can play with that :). The token is stored in Redis using Spring Boot Starter Security and you have to define our own UserDetailsService which you pass into AuthenticationManagerBuilder.

    Long story short - copy paste EmbeddedRedisConfiguration and SecurityConfig and replace AuthenticationManagerBuilder to your logic.

    HTTP:

    Requesting token - sending basic HTTP auth content in a request header. A token is given back in a response header.

    http --print=hH -a user:password localhost:8080/v1/users
    
    GET /v1/users HTTP/1.1
    Accept: */*
    Accept-Encoding: gzip, deflate
    Authorization: Basic dXNlcjpwYXNzd29yZA==
    Connection: keep-alive
    Host: localhost:8080
    User-Agent: HTTPie/0.9.3
    
    HTTP/1.1 200 OK
    Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    Content-Length: 4
    Content-Type: text/plain;charset=UTF-8
    Date: Fri, 06 May 2016 09:44:23 GMT
    Expires: 0
    Pragma: no-cache
    Server: Apache-Coyote/1.1
    X-Application-Context: application
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    X-XSS-Protection: 1; mode=block
    x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af
    

    Same request but using token:

    http --print=hH localhost:8080/v1/users 'x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af'
    
    GET /v1/users HTTP/1.1
    Accept: */*
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Host: localhost:8080
    User-Agent: HTTPie/0.9.3
    x-auth-token:  cacf4a97-75fe-464d-b499-fcfacb31c8af
    
    HTTP/1.1 200 OK
    Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    Content-Length: 4
    Content-Type: text/plain;charset=UTF-8
    Date: Fri, 06 May 2016 09:44:58 GMT
    Expires: 0
    Pragma: no-cache
    Server: Apache-Coyote/1.1
    X-Application-Context: application
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    X-XSS-Protection: 1; mode=block
    

    If you pass wrong username/password or token you get 401.

    JAVA

    I added those dependencies into build.gradle

    compile("org.springframework.session:spring-session-data-redis:1.0.1.RELEASE")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("com.github.kstyrc:embedded-redis:0.6")
    

    Then Redis configration

    @Configuration
    @EnableRedisHttpSession
    public class EmbeddedRedisConfiguration {
    
        private static RedisServer redisServer;
    
        @Bean
        public JedisConnectionFactory connectionFactory() throws IOException {
            redisServer = new RedisServer(Protocol.DEFAULT_PORT);
            redisServer.start();
            return new JedisConnectionFactory();
        }
    
        @PreDestroy
        public void destroy() {
            redisServer.stop();
        }
    
    }
    

    Security config:

    @Configuration
    @EnableWebSecurity
    @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        UserService userService;
    
        @Override
        protected void configure(AuthenticationManagerBuilder builder) throws Exception {
            builder.userDetailsService(userService);
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .requestCache()
                    .requestCache(new NullRequestCache())
                    .and()
                    .httpBasic();
        }
    
        @Bean
        public HttpSessionStrategy httpSessionStrategy() {
            return new HeaderHttpSessionStrategy();
        }
    }
    

    Usually in tutorials you find AuthenticationManagerBuilder using inMemoryAuthentication but there is a lot more choices (LDAP, ...) Just take a look into class definition. I'm using userDetailsService which requires UserDetailsService object.

    And finally my user service using CrudRepository.

    @Service
    public class UserService implements UserDetailsService {
    
        @Autowired
        UserRepository userRepository;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            UserAccount userAccount = userRepository.findByEmail(username);
            if (userAccount == null) {
                return null;
            }
            return new User(username, userAccount.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
        }
    }
    

提交回复
热议问题