# spring-security-gitee-experiment-4
**Repository Path**: chenbairui/spring-security-gitee-experiment4
## Basic Information
- **Project Name**: spring-security-gitee-experiment-4
- **Description**: 基于Spring Security码云OAuth2认证的实验仓库
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-05-15
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
#
东莞理工学院网络空间安全学院
#### 课程名称:企业级开发框架专题 学期:2020春季
|
实验名称 | 基于Spring Security码云OAuth2认证 | 实验序号 | 四 |
|
姓 名 | 陈伯瑞 | 学 号 | 201741412102 | 班 级 | 17软卓1班 |
|
实验地点 | 在家 | 实验日期 | 2020/05/14 | 指导老师 | 黎志雄 |
|
教师评语 | | 实验成绩 | 评阅教师 |
| 百分制 | |
|
同组同学 | 无 |
### 一、 实验目的
1. 掌握使用Spring Security框架;
2. 掌握配置Spring Security的安全过滤链;
3. 掌握编写Spring Security单元测试;
4. 掌握创建接入码云的应用;
5. 掌握码云OAuth2认证基本流程;
6. 掌握使用码云API;
7. 了解使用模板引擎或前端框架制作用户登录界面。
### 二、 实验环境
1. JDK 1.8或更高版本
2. Maven 3.6+
3. IntelliJ IDEA
### 三、 实验任务
1. 创建接入码云的应用

///////////////////////////////////////////////////
/// 步骤一:创建接入码云的应用
/// 参考:https://gitee.com/api/v5/oauth_doc#/list-item-3
static final String CLIENT_ID = "ecb3da4acf74fa35ad5d31ca59c78260824ab934908a3bfa5738a8436af5e036";
static final String CLIENT_SECRET = "edaeb8d09dd56e3b0d7dfdf3b2ef2226ed973c8cc8c7d50c6d1cf94f47e88dba";
///////////////////////////////////////////////////
2. 编写重定向过滤器的业务逻辑。
当用户访问/oauth2/gitee时,本重定向过滤器拦截请求,并将用户重定向到码云三方认证页面上
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 如果不是/oauth2/gitee请求,则继续下一个过滤器。
if (!request.getRequestURI().endsWith(DEFAULT_AUTHORIZATION_REQUEST_BASE_URI)) {
filterChain.doFilter(request, response);
return;// 执行完安全过滤器链后不执行后面的代码。
}
// 应用通过 浏览器 或 Webview 将用户引导到码云三方认证页面上( GET请求 )
// 重定向地址:https://gitee.com/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code
//////////////////////////////////////////////////////////////
/// 步骤二:编写重定向过滤器的业务逻辑。
/// 当用户访问/oauth2/gitee时,本重定向过滤器拦截请求,并将用户重定向到码云三方认证页面上。
//////////////////////////////////////////////////////////////
String url = UriComponentsBuilder.fromUriString(AUTHORIZE_URL)
.buildAndExpand(CLIENT_ID, REDIRECT_URI).toString();
response.sendRedirect(url);
}
}
3. 使用码云access_token API向码云认证服务器发送post请求获取access_token
private String getAccessToken(String code) {
////////////////////////////////////////////////////
/// 步骤三:使用码云access_token API向码云认证服务器发送post请求获取access_token。
String url = UriComponentsBuilder.fromUriString(ACCESS_TOKEN_API_URI)
.buildAndExpand(code, CLIENT_ID, REDIRECT_URI, CLIENT_SECRET).toString();
RequestEntity requestEntity = RequestEntity.post(URI.create(url))
.headers(httpHeaders -> httpHeaders.add("User-Agent", "Mozilla/5.8"))
.build();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity responseEntity = restTemplate.exchange(requestEntity, String.class);
String body = responseEntity.getBody();
Map parseMap = new JacksonJsonParser(new ObjectMapper()).parseMap(body);
String accessToken= (String) parseMap.get("access_token");
return accessToken;
////////////////////////////////////////////////////
// 正确返回的access_token的json字符串:
// {"access_token":"7282a1140867f6e3527f805af1950ea8","token_type":"bearer","expires_in":86400,"refresh_token":"0664cd3b66e36943b341285764a257ccfc7265a319dfcdd93c5f1bfbd4e023f1","scope":"user_info","created_at":1589124246}
}
4. 使用码云API获取授权用户的资料
private Map getUserInfo(String accessToken) {
////////////////////////////////////////////////////
/// 步骤四:使用码云API获取授权用户的资料。
/// 参考:https://gitee.com/api/v5/swagger#/getV5User
String url = UriComponentsBuilder.fromUriString(USER_INFO_URI)
.buildAndExpand(accessToken).toString();
RequestEntity requestEntity = RequestEntity.get(URI.create(url))
.headers(httpHeaders -> { httpHeaders.add("User-Agent", "Mozilla/5.8"); })
.build();
RestTemplate rest = new RestTemplate();
ResponseEntity responseEntity = rest.exchange(requestEntity, String.class);
String body = responseEntity.getBody();
return new JacksonJsonParser(new ObjectMapper()).parseMap(body);
////////////////////////////////////////////////////
}
5. 把自定义的两个Filter加进安全过滤链
@Override
public void configure(H http) {
////////////////////////////////////////////////////////////////
/// 步骤五:把自定义的两个Filter加进安全过滤链
/// 注意:不要加在SecurityContextPersistenceFilter前面就行。
////////////////////////////////////////////////////////////////
http.addFilterAfter(postProcess(new GiteeOAuth2LoginAuthenticationFilter()), HeaderWriterFilter.class)
.addFilterAfter(postProcess(new GiteeOAuth2RedirectFilter()),HeaderWriterFilter.class);
}
6. 把我们自定义的SecurityConfigurer应用到安全过滤链
@Override
protected void configure(HttpSecurity http) throws Exception {
// 不指定path,本安全过滤链会匹配所有请求。
http
.authorizeRequests()
.antMatchers("/").permitAll()// 首页放行
.anyRequest().hasAnyAuthority("USER").and()
.formLogin()
.loginPage("/user/login_frontend").permitAll()
.defaultSuccessUrl("/user").and()
.logout()
.logoutUrl("/user/logout").permitAll()
.logoutSuccessUrl("/user/login_frontend?logout").and()
// 自定义访问拒绝异常处理逻辑
.exceptionHandling().accessDeniedHandler(UserSecurityConfig::accessDeniedHandle)
////////////////////////////////////////////////
/// 步骤六:把我们自定义的SecurityConfigurer应用到安全过滤链
////////////////////////////////////////////////
.and()
.apply(new GiteeOAuth2LoginConfigurer<>())
;
}
7. 改造/user接口,返回码云用户资料给前端;改造user.ftlh模板用于显示用户资料。
@GetMapping("/user")
String userIndex(Model model) {
////////////////////////////////////
/// 步骤七:改造/user接口,返回码云用户资料给前端;改造user.ftlh模板用于显示用户资料。
////////////////////////////////////
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
try{
Map userInfo = (Map) authentication.getPrincipal();
model.addAttribute("userInfo",userInfo);
} catch (Exception e){
/*此处是本地登录*/
return "user";
}
return "userGitee";
}
8. 编写单元测试。模拟一个登录用户,访问受保护的接口/test,断言接口的返回内容body部分是否一致。
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class SpringSecurityGiteeExperimentApplicationTests {
///// Setting Up MockMvc and Spring Security. start
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
// 在本类中,每次执行测试都先调用@BeforeEach注解的方法
@BeforeEach
public void init() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
///// Setting Up MockMvc and Spring Security. end
/**
* 测试限权接口
*
* 模拟一个登录用户,访问受保护的接口。
*
* @see WithMockUser
* @see MockMvc#perform(RequestBuilder)
* @see MockMvcRequestBuilders#get(String, Object...)
*/
@Test
public void test() throws Exception {
////////////////////////////////////////////
/// 步骤八:模拟一个登录用户,访问受保护的接口/test,断言接口的返回内容body部分是否一致。
////////////////////////////////////////////
mvc.perform(MockMvcRequestBuilders.get("/test"))
.andExpect(status().isOk())
.andExpect(content().string("访问/test接口成功,你拥有USER权限"));
}
@Test
public void test2() throws Exception {
// 测试登录成功
mvc.perform(formLogin("/login").user("user").password("user"))
.andExpect(unauthenticated());
}
}
9. 运行效果:
