Spring Security 从单体到微服务的演进 - 单体web

参考:Spring Security and Angular JS
代码:codes on github

术语

CSRF - Cross-Site Request Forgery (跨站请求伪造)
CORS - Cross Origin Resource Sharing (跨域资源共享)

单体web app

创建spring web工程。 采用Spring CLI,(其他多种方式请自便)

spring init --dependencies web,security ui

OOTB security

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class UIApplication {

    public static void main(String[] args) {
        SpringApplication.run(UIApplication.class, args);
    }

    @RequestMapping("/")
    public ResponseEntity home() {
        return ResponseEntity.ok("hello");
    }
}

OOTB的情况,pom引入了spring security的依赖,不做任何配置。所有的请求都需要登录。

E:\>curl "http://localhost:8080"
{"timestamp":1478501120211,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/"}

从浏览器打开会提示输入用户名和密码,用户名是user,密码可以从console中看到:

Using default security password: 60fdacf5-347d-4f6f-ac7c-bc0a9725bc55

Look under the hood: 没有任何个性化的配置的时候AuthenticationManagerConfiguration 会采用默认的配置。默认的security配置参考:SecurityProperties类。prefix = “security”。 可通过一下配置修改OOTB的用户名和密码。

security.user.name=joe
security.user.password=123456

自定义security

参考注解: @EnableWebSecurity

package com.example.security;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @author Joe
 *
 */
@EnableWebSecurity
public class ExtWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {


    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
        // Spring Security should completely ignore URLs starting with /resources/
                .antMatchers("/resources/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .and()
                // Possibly more configuration ...
                .formLogin() // enable form based log in
                // set permitAll for all URLs associated with Form Login
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // enable in memory based authentication with a user named "user" and "admin"
        auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
                .and().withUser("admin").password("password").roles("USER", "ADMIN");
    }

    // Possibly more overridden methods ...

}

添加对应URL的接口

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class UIApplication {

    public static void main(String[] args) {
        SpringApplication.run(UIApplication.class, args);
    }

    @GetMapping("/public/hello")
    public ResponseEntity home() {
        return ResponseEntity.ok("Hello Every one!");
    }

    @GetMapping("/admin/hello")
    public ResponseEntity admin() {
        return ResponseEntity.ok("Hello Admin!");
    }
}

/public/hello 可以随意访问,/admin/hello 需要登录用户有admin的role。用user登录会抛出403的错误。

配置中加了.formLogin().permitAll() 未登录用户会直接重定向至OOTB的登录页面。去掉.formLogin().permitAll() 后直接访问/admin/hello会直接抛出403, 而非401 (这点笔者暂时未能理解,未登录应该抛出401 才对,如果系统中加了授权需要区分这两个状态)(什么场景不需要formLogin?用户如何登陆?满足这个场景的web app往往不是单体,可能是某个微服务,提供restful services)。
这里要明白一点:一旦定制,OOTB的行为就会受影响,尽管看似你没有改.
比如:不加.formLogin().permitAll() 就会导致没有登录提示框出现,访问需要授权的服务会直接抛出403

关于如何配置login的page等参考:Java Configuration and Form Login

Spring Boot CORS 解决Trick:

官方示例
官方示例中只提到了 WebMvcConfigurerAdapter, 然而大部分程序都有WebSecurityConfigurerAdapter 相关的配置

@Configuration
@EnableWebSecurity
public class HrWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.cors().and().authorizeRequests().antMatchers("/hr/**").authenticated();
    }
}

上面代码的:http.cors() 非常重要, 这里java doc拷贝出来

Adds a CorsFilter to be used. If a bean by the name of corsFilter is provided, that CorsFilter is used. Else if corsConfigurationSource is defined, then that CorsConfiguration is used. Otherwise, if Spring MVC is on the classpath a HandlerMappingIntrospector is used.

从注解上可以看出来,我们需要一个corsFilter:

@Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

如果用到了zuul, 上面的配置也不能生效。
stackoverflow 这个issue
(zuul 用在apigateway的时候,其他微服务不需要有CORS)
Ticket on GitHub

你可能感兴趣的:(Spring Security 从单体到微服务的演进 - 单体web)