Spring Security 之API 项目安全验证(基于basic-authentication)
===================================
Basic Authorization 规范
===================================
Request 头部:
Authorization: Basic QWxpY2U6MTIzNDU2
其中 QWxpY2U6MTIzNDU2 是user:pwd做 base64 编码, 格式是 user:pwd
response 头部:
WWW-Authenticate: Basic realm="My Realm"
按照 RFC 规范, 相同的 realm(域) 下的web page 将共享同样的 credentials, 所以推荐 realm 取值为 application name. realm 大小写敏感, 可以包含空格.
===================================
Rest API 示例
===================================
功能:
1. 演示如何启用 Basic Authorization
2. 如何使用 RestTemplate 访问受保护的 API接口
-----------------------------------
SecurityConfig 代码
-----------------------------------
关键点有:
0. 对于 /api/** 需要 ROLE_ADMIN 角色的账号访问, 对于 /guest/** 路径允许匿名访问.
1. 使用 HttpSecurity.httpBasic() 启用 Basic Authorization.
2. 使用 HttpSecurity.httpBasic().realmName() 设置 realm.
3. 使用 HttpSecurity.httpBasic().authenticationEntryPoint() 设置 BasicAuthenticationEntryPoint 对象, 如果一个请求通过验证, 该对象会自动为web response设定 WWW-Authenticate header, 如果未通过, 该对象会自动将HttpStatus设置为UNAUTHORIZED.
4. 显式启用了 STATELESS session 管理机制, 经测试,Spring Security 在Basic Authorization模式下, session自动就处于了 STATELESS 状态.
5. 对于 HttpMethod.OPTIONS 请求, 允许匿名访问. API 项目应该开放 OPTIONS 查询权限.
@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { //@formatter:off @Override public void configure(AuthenticationManagerBuilder builder) throws Exception { builder.inMemoryAuthentication() .withUser("123").password("123").roles("USER") .and() .withUser("ADMIN").password("ADMIN").roles("ADMIN"); } //@formatter:on private static String REALM = "MY SPRING SECURITY DEMO"; // @formatter:off @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 对于/api 路径下的访问需要有 ROLE_ADMIN 的权限 .antMatchers("/api/**").hasRole("ADMIN") // 对于/guest 路径开放访问 .antMatchers("/guest/**").permitAll() // 其他url路径之需要登陆即可. .anyRequest().authenticated() .and() //启用 basic authentication .httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthenticationEntryPoint()) .and() //不创建 session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } // @formatter:on /** * 生成 BasicAuthenticationEntryPoint 对象, 在该对象的支持下, 通过验证的请求, 返回的response 将会自动加上 * WWW-Authenticate Header. 在该对象的支持下, 未通过验证的请求, 返回的 response 为 UNAUTHORIZED 错误. * * @return */ @Bean public BasicAuthenticationEntryPoint getBasicAuthenticationEntryPoint() { BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); entryPoint.setRealmName(REALM); return entryPoint; } /* * 开放 Options 请求 */ @Override public void configure(WebSecurity web) throws Exception { // TODO Auto-generated method stub web.ignoring() .antMatchers(HttpMethod.OPTIONS, "/**"); } @SuppressWarnings("deprecation") @Bean public NoOpPasswordEncoder passwordEncoder() { BasicAuthenticationEntryPoint a; return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); } }
-----------------------------------
受控 API Rest 类
-----------------------------------
对于 /api/** url需要 ROLE_ADMIN 角色的账号访问, 这里的代码很简单.
/* * 需要进行 Basic Auth 的API */ @RestController @RequestMapping("/api") class ApiController {
@GetMapping("/books") public String getBooks() { return "API book"; } }
直接访问受控url, 弹出浏览器内置的登陆框, 见下图, 符合预期.
-----------------------------------
RestTemplate 访问 Basic Authorization的API
-----------------------------------
关键点:
使用了 restTemplateBuilder.basicAuthorization(user,pwd).build() 来构建 RestTemplate, 这样的 RestTemplate 会自动在Request上加 Authorization Header.
/* * RestTemplate 访问 Basic Authorization的API的示例 */ @RestController @RequestMapping("/guest") class DefaultController { @GetMapping("/mybooks") @ResponseBody public String getMyBook() { return "My Book"; } private RestTemplate restTemplate; @Autowired private RestTemplateBuilder restTemplateBuilder; @Autowired void setRestTemplate(RestTemplateBuilder restTemplateBuilder) { restTemplate = restTemplateBuilder.basicAuthorization("ADMIN", "ADMIN") .build(); } @GetMapping("/apibooks") @ResponseBody public String getApiBooks() { return restTemplate.getForObject("http://localhost:8080/api/books", String.class); } }
访问 http://localhost:8080/guest/apibooks 地址, 无需登陆即可得到 api 结果, 见下图, 符合预期.
===================================
参考
===================================
http://websystique.com/spring-security/secure-spring-rest-api-using-basic-authentication/
http://www.bytestree.com/spring/restful-web-services-authentication-authorization/
https://www.baeldung.com/spring-security-basic-authentication