()Spring Security简介
简介
Spring Security是一个专注于为Java应用程序提供身份认证和授权的框架,它的强大之处在于它可以轻松扩展以满足自定义的需求。
特点
- 对身份的认证和授权提供全面的、可扩展的支持。
- 防止各种攻击,如会话固定攻击、点击劫持、csrf攻击等。
- 支持Servlet API、SpringMVC等Web技术集成。
SpringBoot整合Security
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.6.6</version>
</dependency>
2、用户类实现UserDetails接口,并实现方法
/**
*
* @return true:账号未过期
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
*
* @return true:账号未锁定
*/
@Override
public boolean isAccountNonLocked() {
return false;
}
/**
*
* @return true:凭证未过期
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
*
* @return true:账号可用
*/
@Override
public boolean isEnabled() {
return true;
}
public void setUsername(String username) {
this.username = username;
}
/**
*
* @return 返回用户的权限集合(一个用户可以有多个权限)
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list=new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
//type=1时代表用户为超级管理员,2代表普通用户
switch (type){
case 1:
return "ADMIN";
default:
return "USER";
}
}
});
return list;
}
3、UserService实现UserDetailsService接口并实现接口方法(能够自动判断用户登陆时账号密码是否正确)
/**
* 此方法底层可以自动判断账号与密码是否正确
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
4、编写一个SecurityConfig配置类,来配置相关信息
package com.nowcoder.community.config;
import com.nowcoder.community.entity.User;
import com.nowcoder.community.service.UserService;
import com.nowcoder.community.util.CommunityUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
import sun.security.krb5.Config;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
public void configure(WebSecurity web) throws Exception {
//忽略静态资源的访问
web.ignoring().antMatchers("/resources/**");
}
/**
* AuthenticationManager:认证的接口
* AuthenticationManagerBuilder:创建AuthenticationManager的工具
* ProvideManager:AuthenticationManager接口的实现类
* **/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//内置的认证规则
//Pbkdf2PasswordEncoder加密方式,盐:1234
auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345"));
//自定义认证规则
//AuthenticationProvider:ProvideManager持有一组AuthenticationProvider,每个AuthenticationProvider负责一组认证,为了兼容所有的认证
//委托模式:ProvideManager将认证委托给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(authentication.getName());
if(user==null){
throw new UsernameNotFoundException("账号不存在!");
}
password = CommunityUtil.md5(password + user.getSalt());
if(password!=user.getPassword()){
throw new BadCredentialsException("密码不正确!");
}
//参数:principal:主要信息,credentials证书,authorities权限
return new UsernamePasswordAuthenticationToken(user,password,user.getAuthorities());
}
//返回支持的哪中认证类型(以下支持账号密码认证)
@Override
public boolean supports(Class<?> authentication) {
//UsernamePasswordAuthenticationToken:Authentication接口常用实现类
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//登录相关配置
http.formLogin()
.loginPage("/loginpage")//登录页面
.loginProcessingUrl("/login")//拦截的请求路径
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.sendRedirect(httpServletRequest.getContextPath()+"/index");//重定向到首页,属于不同的请求
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
request.setAttribute("error",e.getMessage());//设置错误信息参数
request.getRequestDispatcher("/loginpage").forward(request,response);//请求转发属于同一次请求,可以携带参数
}
});
//退出相关配置
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");//拒绝访问时给用户返回一个提示页面
//增加Filter,处理验证码
http.addFilterBefore(new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//将ServletRequest和ServletResponse对象转成子类(HttpServletRequest,HttpServletResponse)子类的方法丰富
HttpServletRequest request=(HttpServletRequest)servletRequest;
HttpServletResponse response=(HttpServletResponse) servletResponse;
if(request.getServletPath().equals("/login")){
String verifyCode=request.getParameter("verifyCode");
//因为是测试,所以将验证码写死为1234
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);
}
}