![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/ed57186dd79ae9ca07e2a2ce5589478a.png)
Spring Security简单使用
资料
实体类
public class User implements UserDetails {
private int id;
private String username;
private String password;
private String salt;
private String email;
private int type;
private int status;
private String activationCode;
private String headerUrl;
private Date createTime;
...
// true: 账号未过期.
@Override
public boolean isAccountNonExpired() {
return true;
}
// true: 账号未锁定.
@Override
public boolean isAccountNonLocked() {
return true;
}
// true: 凭证未过期.
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// true: 账号可用.
@Override
public boolean isEnabled() {
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
switch (type) {
case 1:
return "ADMIN";
default:
return "USER";
}
}
});
return list;
}
}
Service
UserService 实现UserDetailsService 接口
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
public User findUserByName(String username) {
return userMapper.selectByName(username);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.findUserByName(username);
}
}
配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略静态资源的访问
web.ignoring().antMatchers("/resources/**");
}
// AuthenticationManager: 认证的核心接口.
// AuthenticationManagerBuilder: 用于构建AuthenticationManager对象的工具.
// ProviderManager: AuthenticationManager接口的默认实现类.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 内置的认证规则:此方法密码的加密salt是固定的
// UserService 必须实现UserDetailsService 接口
// auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345"));
// 自定义认证规则
// AuthenticationProvider: ProviderManager持有一组AuthenticationProvider,每个AuthenticationProvider负责一种认证.
// 委托模式: ProviderManager将认证委托给AuthenticationProvider.
auth.authenticationProvider(new AuthenticationProvider() {
// Authentication: 用于封装认证信息的接口,不同的实现类代表不同类型的认证信息.
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
User user = userService.findUserByName(username);
if (user == null) {
throw new UsernameNotFoundException("账号不存在!");
}
password = CommunityUtil.md5(password + user.getSalt());
if (!user.getPassword().equals(password)) {
throw new BadCredentialsException("密码不正确!");
}
// principal: 主要信息; credentials: 证书; authorities: 权限;
return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
}
// 当前的AuthenticationProvider支持哪种类型的认证.
@Override
public boolean supports(Class<?> aClass) {
// UsernamePasswordAuthenticationToken: Authentication接口的常用的实现类.
return UsernamePasswordAuthenticationToken.class.equals(aClass);
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 登录相关配置
http.formLogin()
.loginPage("/loginpage") // 登录页面的请求
.loginProcessingUrl("/login") // 登录处理路径,拦截该路径进行认证(???)
.successHandler(new AuthenticationSuccessHandler() { // 成功会跳转到index
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getContextPath() + "/index"); // 这里是重定向到index
}
})
.failureHandler(new AuthenticationFailureHandler() { // 失败会对哪里不正确进行提示
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
request.setAttribute("error", e.getMessage()); //将参数绑定到request
request.getRequestDispatcher("/loginpage").forward(request, response); // 再转发到loginpage
}
});
// 退出相关配置
http.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getContextPath() + "/index"); // 退出成功后重定向到首页
}
});
// 授权配置
http.authorizeRequests()
.antMatchers("/letter").hasAnyAuthority("USER", "ADMIN") // /letter路径必须要有USER/ADMIN权限
.antMatchers("/admin").hasAnyAuthority("ADMIN")
.and().exceptionHandling().accessDeniedPage("/denied"); //权限不够会跳转到/denied
// 增加Filter,处理验证码
// 验证码应该在账号认证前
http.addFilterBefore(new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse; // 先转成HttpServletResponse接口,它是ServletRequest的子接口
//如果是登录请求则验证 验证码
if (request.getServletPath().equals("/login")) { //个人理解,/login路径是个中转站
String verifyCode = request.getParameter("verifyCode");
if (verifyCode == null || !verifyCode.equalsIgnoreCase("1234")) {
request.setAttribute("error", "验证码错误!");
request.getRequestDispatcher("/loginpage").forward(request, response);
return;
}
}
// 让请求继续向下执行.
filterChain.doFilter(request, response);
}
}, UsernamePasswordAuthenticationFilter.class);
// 记住我
http.rememberMe()
.tokenRepository(new InMemoryTokenRepositoryImpl())
.tokenValiditySeconds(3600 * 24)
.userDetailsService(userService);
}
}
Spring Security配置类:
一、继承WebSecurityConfigurerAdapter接口
WebSecurityConfigurerAdapter 提供了一种便利的方式去创建 WebSecurityConfigurer的实例,只需要重写 WebSecurityConfigurerAdapter 的方法,即可配置拦截什么URL、设置什么权限等安全控制。
1、注入UserService
2、忽略静态资源的访问的拦截
3、认证:重写 protected void configure(AuthenticationManagerBuilder auth) 。
由于系统中,密码由md5算法加密,所以需要自定义认证规则。ProviderManager持有一组AuthenticationProvider,每个AuthenticationProvider负责一种认证,所以auth.authenticationProvider(new AuthenticationProvider() {…}
Authentication: 用于封装认证信息的接口,不同的实现类代表不同类型的认证信息。最后返回UsernamePasswordAuthenticationToken,里面包含了principal: 主要信息; credentials: 证书; authorities: 权限;
用户表的密码通常使用MD5等不可逆算法加密后存储,为防止彩虹表破解更会先使用一个特定的字符串(如域名)加密,然后再使用一个随机的salt(盐值)加密。
特定字符串是程序代码中固定的,salt是每个密码单独随机,一般给用户表加一个字段单独存储,比较麻烦。
BCrypt算法将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题。
4、授权
A)loginProcessingUrl()这个方法可以看作一个中转站,前台界面提交表单之后跳转到这个路径进行User DetailsService的验证,如果成功, defaultSuccessUrl()如果失败,那么转向failureUrl("/error.html"),我们需要注意的就是这里的action现需要和loginProcessingUrl()中的参数保持一致
<!-- loginpage.html -->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录社区</h1>
<form method="post" th:action="@{/login}">
<p style="color:red;" th:text="${error}">
<!--提示信息-->
</p>
<p>
账号:<input type="text" name="username" th:value="${param.username}">
</p>
<p>
密码:<input type="password" name="password" th:value="${param.password}">
</p>
<p>
验证码:<input type="text" name="verifyCode"> <i>1234</i>
</p>
<p>
<input type="checkbox" name="remember-me"> 记住我
</p>
<p>
<input type="submit" value="登录">
</p>
</form>
</body>
</html>
B)request.getServletPath()使用说明 返回的是String类型
C)Spring Security要求退出是post请求。post是提交表单,不方便直接写超链接,所以用js处理
index.html
<h1>社区首页</h1>
<!--欢迎信息-->
<p th:if="${loginUser!=null}">
欢迎你, <span th:text="${loginUser.username}"></span>!
</p>
<ul>
<li><a th:href="@{/discuss}">帖子详情</a></li>
<li><a th:href="@{/letter}">私信列表</a></li>
<li><a th:href="@{/loginpage}">登录</a></li>
<!--<li><a th:href="@{/loginpage}">退出</a></li>-->
<li>
<form method="post" th:action="@{/logout}">
<!--提交第一个form-->
<a href="javascript:document.forms[0].submit();">退出</a>
</form>
</li>
</ul>