SpringBoot整合SpringSecurity(一)入门程序

Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。

Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求。

这是Spring Security 官方的说明,早就听闻 Spring Security 功能强大但上手困难,于是上手学习了学习,在此整理出几篇文章,百度的坑是真的多。

本文章代码可以参考https://gitee.com/Maoxs/security-test中的 security-form

首先依赖

<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的个性化配置

把我们自定义的 userDetailsServiceSuccessAuthenticationHandlerFailureAuthenticationHandler注入进来

这里我们还指定了密码的加密方式(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 如果有什么不对的请在下方留言。