Spring Boot 整合 Spring Security ,配置登入/登出,如:登入接口,登入成功或失敗後的響應等。
1 建立工程
建立 Spring Boot 項目
spring-boot-springsecurity-login
,添加
Web/Spring Security
依賴,如下:
最終的依賴如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2 配置 Spring Security
新增
SecurityConfig
配置類,如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();// 密碼不加密
return new BCryptPasswordEncoder();// 密碼加密
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 在記憶體中配置 2 個使用者
/*auth.inMemoryAuthentication()
.withUser("admin").password("123456").roles("admin")
.and()
.withUser("user").password("123456").roles("user");// 密碼不加密*/
auth.inMemoryAuthentication()
.withUser("admin").password("$2a$10$fB2UU8iJmXsjpdk6T6hGMup8uNcJnOGwo2.QGR.e3qjIsdPYaS4LO").roles("admin")
.and()
.withUser("user").password("$2a$10$3TQ2HO/Xz1bVHw5nlfYTBON2TDJsQ0FMDwAS81uh7D.i9ax5DR46q").roles("user");// 密碼加密
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 開啟登入配置
http.authorizeRequests()
// 表示 admin 角色能通路
.antMatchers("/admin/**").hasRole("admin")
// 表示 admin 或 user 角色都能通路
// .antMatchers("/user/**").hasAnyRole("admin", "user")
// 表示 admin 或 user 角色都能通路
.antMatchers("/user/**").access("hasAnyRole('admin','user')")
// 表示剩餘的其他接口,登入之後就能通路
.anyRequest().authenticated()
.and()
.formLogin()
// 表示登入頁的位址,例如當你通路一個需要登入後才能通路的資源時,系統就會自動給你通過【重定向】跳轉到這個頁面上來
.loginPage("/login")
// 表示處理登入請求的接口位址,預設為 /login
.loginProcessingUrl("/doLogin")
// 定義登入時,使用者名的 key,預設為 username
.usernameParameter("uname")
// 定義登入時,密碼的 key,預設為 password
.passwordParameter("passwd")
// 登入成功的處理器
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Map<String, Object> map = new HashMap<>();
map.put("status", 200);
map.put("msg", authentication.getPrincipal());
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
})
// 登入失敗的處理器
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Map<String, Object> map = new HashMap<>();
map.put("status", 401);
if (e instanceof LockedException) {
map.put("msg", "賬戶被鎖定,登入失敗!");
} else if (e instanceof BadCredentialsException) {
map.put("msg", "使用者名或密碼輸入錯誤,登入失敗!");
} else if (e instanceof DisabledException) {
map.put("msg", "賬戶被禁用,登入失敗!");
} else if (e instanceof AccountExpiredException) {
map.put("msg", "賬戶過期,登入失敗!");
} else if (e instanceof CredentialsExpiredException) {
map.put("msg", "密碼過期,登入失敗!");
} else {
map.put("msg", "登入失敗!");
}
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
})
// 和表單登入相關的接口統統都直接通過
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
// 登出成功的處理器
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Map<String, Object> map = new HashMap<>();
map.put("status", 200);
map.put("msg", "登出登入成功!");
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
})
.permitAll()
.and()
.csrf().disable()
.exceptionHandling()
// 無通路權限的處理器
.accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest req, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Map<String, Object> map = new HashMap<>();
map.put("status", 403);
map.put("msg", "無通路權限!");
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
})
// 預設情況下使用者直接通路一個需要認證之後才可以通路的請求時,會被重定向到.loginPage("/login"),前後端分離時會導緻跨域。
// 增加如下配置後,就不會發生重定向操作了,服務端會直接給浏覽器一個 JSON 提示
.authenticationEntryPoint(new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Map<String, Object> map = new HashMap<>();
map.put("status", 401);
if (authException instanceof InsufficientAuthenticationException) {
map.put("msg", "通路失敗,請先登入!");
} else {
map.put("msg", "通路失敗!");
}
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
});
}
@Override
public void configure(WebSecurity web) throws Exception {
// 配置不需要攔截的請求位址,即該位址不走 Spring Security 過濾器鍊
web.ignoring().antMatchers("/vercode");
}
}
3 測試
新增
HelloController
測試類,如下:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
@GetMapping("/admin/hello")
public String admin() {
return "hello admin";
}
@GetMapping("/user/hello")
public String user() {
return "hello user";
}
@GetMapping("/login")
public String login() {
return "please login";
}
}
項目啟動之後,用
Postman
完成測試,如下:
通路
/hello
接口,提示先登入。
通路
/doLogin
接口登入失敗,因為
key
不對。
用自定義的
key
通路
/doLogin
接口登入成功。
再通路
/hello
接口,傳回正常。
- Spring Boot 教程合集(微信左下方閱讀全文可直達)。
- Spring Boot 教程合集示例代碼:https://github.com/cxy35/spring-boot-samples
- 本文示例代碼:https://github.com/cxy35/spring-boot-samples/tree/master/spring-boot-security/spring-boot-springsecurity-login
掃碼關注微信公衆号 程式員35 ,擷取最新技術幹貨,暢聊 #程式員的35,35的程式員# 。獨立站點:https://cxy35.com