Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。
Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求。
这是Spring Security 官方的说明,早就听闻 Spring Security 功能强大但上手困难,于是上手学习了学习,在此整理出几篇文章,百度的坑是真的多。
代码请参考 https://github.com/AutismSuperman/springsecurity-example
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<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>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hellotitle>
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);
}
}
要想通过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()));
}
}
上面做的那么多但是并没有跟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 如果有什么不对的请在下方留言。