序言
Spring Security是一個功能強大且可高度自定義的身份驗證和訪問控制框架。它是保護基於Spring的應用程序的事實標準。
Spring Security是一個專注於爲Java應用程序提供身份驗證和授權的框架。與所有Spring項目一樣,Spring Security的真正強大之處在於它可以輕鬆擴展以滿足自定義要求。
這是Spring Security 官方的說明,早就聽聞 Spring Security 功能強大但上手困難,於是上手學習了學習,在此整理出幾篇文章,百度的坑是真的多。
代碼請參考 https://github.com/AutismSuperman/springsecurity-example
首先依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
準備頁面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登錄</title>
</head>
<body>
<h3>表單登錄</h3>
<table>
<tr>
<td>用戶名:</td>
<td><input type="text" autocomplete="off" name="username"></td>
</tr>
<tr>
<td>密碼:</td>
<td><input type="password" autocomplete="off" name="password"></td>
</tr>
<tr>
<td colspan="2">
<button type="button" onclick="login()">登錄</button>
</td>
</tr>
</table>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
function login() {
var username = $("input[name=username]").val();
var password = $("input[name=password]").val();
if (username === "" || password === "") {
alert("用戶名或密碼不能爲空");
return;
}
$.ajax({
type: "POST",
url: "/authentication/form",
data: {
"username": username,
"password": password
},
success: function (e) {
alert("登陸成功")
setTimeout(function () {
location.href = '/hello';
}, 1500);
},
error: function (e,a,b) {
console.log(e.responseText);
alert("登陸失敗")
}
});
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello</title>
</head>
<body>
<h2>hello world from fulinlin.</h2>
<a href="/logout">退出登錄</a>
</body>
</html>
然後是controller
@Controller
@RequestMapping
public class TestController {
@RequestMapping("/hello")
public String hello() {
return "hello";
}
@RequestMapping("/login")
public String login() {
return "login";
}
}
準備測試用戶
這裏呢我們就不連數據庫了,創造一些模擬的數據
首先呢是用戶的實體類
@Data
public class SysUser {
private Long id;
private String userName;
private String password;
private List<String> roles;
public SysUser() {
}
public SysUser(Long id, String userName, String password, List<String> roles) {
this.id = id;
this.userName = userName;
this.password = password;
this.roles = roles;
}
}
首先呢是接口
public interface IUserService {
SysUser findByUsername(String userName);
}
然後實現類
@Service
public class UserServiceImpl implements IUserService {
private static final Set<SysUser> users = new HashSet<>();
static {
users.add(new SysUser(1L, "fulin", "123456", Arrays.asList("ROLE_ADMIN", "ROLE_DOCKER")));
users.add(new SysUser(2L, "xiaohan", "123456", Arrays.asList("ROLE_ADMIN", "ROLE_DOCKER")));
users.add(new SysUser(3L, "longlong", "123456", Arrays.asList("ROLE_ADMIN", "ROLE_DOCKER")));
}
@Override
public SysUser findByUsername(String userName) {
return users.stream().filter(o -> StringUtils.equals(o.getUserName(), userName)).findFirst().orElse(null);
}
}
配置SpringSecurity
找到誰
要想通過Security的用戶認證的話 必須要實現一個UserDetailsService
的接口
@Service
public class UserService implements UserDetailsService {
@Autowired
private final IUserService iUserService;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
SysUser user = iUserService.findByUsername(s);
if (user == null) {
throw new UsernameNotFoundException("用戶不存在");
}
//把角色放入認證器裏
Collection<GrantedAuthority> authorities = new ArrayList<>();
List<String> roles = user.getRoles();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return new User(user.getUserName(), user.getPassword(), authorities);
}
}
這裏呢要實現一個返回 UserDetails
類的方法 ,
// Source code recreated from a .class file by IntelliJ IDEA
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
裏面與有各種的用戶狀態 這裏呢
security默認有一個org.springframework.security.core.userdetails.User
的實現 我們返回的時候構建這個類即可,它有三個參數,分別是用戶名、密碼和權限集。
這一驟只是爲了讓security找到你是誰。
登陸成功怎麼辦
security提供了一個AuthenticationSuccessHandler
的接口默認實現是跳轉url,因爲程序走ajax了所以我們返回json
@Component
@Slf4j
public class SuccessAuthenticationHandler implements AuthenticationSuccessHandler {
private final ObjectMapper objectMapper;
public SuccessAuthenticationHandler(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
log.info("登錄成功");
httpServletResponse.setStatus(HttpStatus.OK.value());
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
登陸失敗怎麼辦
與成功處理器對應的還有一個失敗處理器
@Component
@Slf4j
public class FailureAuthenticationHandler implements AuthenticationFailureHandler {
private final ObjectMapper objectMapper;
public FailureAuthenticationHandler(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.info("登錄失敗");
httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(e.getMessage()));
}
}
WebSecurityConfig
上面做的那麼多但是並沒有跟security關聯上
這時候我們要繼承WebSecurityConfigurerAdapter
這個類來實現security的個性化配置
把我們自定義的 userDetailsService
、SuccessAuthenticationHandler
、FailureAuthenticationHandler
注入進來
這裏我們還指定了密碼的加密方式(5.0 版本強制要求設置),因爲我們構造的數據是明文的,所以明文返回即可,如下所示:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private FailureAuthenticationHandler failureAuthenticationHandler;
@Autowired
private SuccessAuthenticationHandler successAuthenticationHandler;
@Autowired
private UserService userService;
/**
* 注入身份管理器bean
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 把userService 放入AuthenticationManagerBuilder 裏
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(
new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//http.httpBasic() //httpBasic 登錄
http.formLogin()
.loginPage("/login")// 登陸的url
.loginProcessingUrl("/authentication/form") // 自定義登錄路徑
.failureHandler(failureAuthenticationHandler) // 自定義登錄失敗處理
.successHandler(successAuthenticationHandler) // 自定義登錄成功處理
.and()
.logout()
.logoutUrl("/logout")
.and()
.authorizeRequests()// 對請求授權
// 這些頁面不需要身份認證,其他請求需要認證
.antMatchers("/login", "/authentication/require",
"/authentication/form").permitAll()
.anyRequest() // 任何請求
.authenticated() // 都需要身份認證
.and()
.csrf().disable();// 禁用跨站攻擊
}
@Override
public void configure(WebSecurity web) throws Exception {
// 設置攔截忽略文件夾,可以對靜態資源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
}
如果你想要將密碼加密,可以修改 configure()
方法如下
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
然後啓動項目會進入登陸頁面,輸入正確的用戶名和密碼即可。
本博文是基於springboot2.x 和security 5 如果有什麼不對的請在下方留言。