Spring Security
• 简介
Security是一个专注于为Java应用程序提供 **身份认证**和**授权**的框架,它的强大之处在于它可以 轻松扩展以满足自定义的需求。
• 特征
- 对身份的 认证 和 授权 提供全面的、可扩展的支持。
- 防止各种攻击,如会话固定攻击、点击劫持、csrf攻击等。
- 支持与Servlet API、Spring
官网:https://spring.io/projects/spring-security
中文学习网址:http://www.spring4all.com/article/428
1、引入Spring Security的依赖
- 引入包的时候,Spring Security权限就会生效 ,会生成随机密码
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.6.4</version>
</dependency>
2、Security授权 需要字符串获取用户的权限
–实现指定Security的接口
–返回用户权限
// 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;
}
//返回用户具备的权限,可以具有很多权限
//通过GrantedAuthority()封装一个权限
@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;
}
Security需要依赖UserDetailsService接口查询用户,所以需要在UserService实现此接口,并且实现其方法,直接返回UserService的查询方法;
@Service
public class UserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.findUserByName(username);
}
}
3、编写Security配置类
编写认证的逻辑
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
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 {
// 内置的认证规则
// 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() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//请求重定向
response.sendRedirect(request.getContextPath() + "/index");
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletRequest.setAttribute("error",e.getMessage());
//请求转发
httpServletRequest.getRequestDispatcher("/loginpage").forward(httpServletRequest,httpServletResponse);
}
});
// 退出相关配置
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")
.antMatchers("/admin").hasAnyAuthority("ADMIN")
.and().exceptionHandling().accessDeniedPage("/denied");
}
Security要求退出必须是POST请求
<li>
<form method="post" th:action="@{/logout}">
<a href="javascript:document.forms[0].submit();">退出</a>
</form>
</li>
4、增加Filter,处理验证码
// 增加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;
//处理登录请求
if (request.getServletPath().equals("/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); //得到用户西信息
lterChain.doFilter(request, response);
}
}, UsernamePasswordAuthenticationFilter.class);
// 记住我
http.rememberMe()
.tokenRepository(new InMemoryTokenRepositoryImpl())
.tokenValiditySeconds(3600 * 24)
.userDetailsService(userService); //得到用户西信息