安全框架,简单说是对访问权限进行控制,应用的安全性包括用户认证(Authentication)
和用户授权(Authorization)
两个部分。
一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
Apache Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro 都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro 要简单的多。
Spring Security 是一个强大的可高度定制的认证和授权框架,对于 Spring 应用来说它是一套 Web 安全标准。Spring Security 注重于为 Java 应用提供认证和授权功能,像所有的Spring项目一样,它对自定义需求具有强大的扩展性。
Apache Shiro 因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。合适的才是最好的!
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
启动日志增加了如下内容,通过该内容可以找到默认
用户名
和密码
- 用户名:UserDetailsServiceAutoConfiguration 类 -> User 类 -> private String name = “user”;
- 密码:5b7773d7-1c0c-432f-9363-de1bd8fe6166
2022-04-22 14:15:46.762 WARN 8284 --- [ main] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: 5b7773d7-1c0c-432f-9363-de1bd8fe6166
This generated password is for development use only. Your security configuration must be updated before running your application in production.
访问:http://127.0.0.1:8080/user/1 接口,跳转到:http://127.0.0.1:8080/login 登录页面(security 默认登录页面),输入用户名、密码即可跳转回接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fhF0abLG-1650617709540)(O:\appCache\Typora\img\【安全篇】Spring Boot 整合 Spring Security 安全框架\image-20220422142048281.png)]
登录后的用户信息默认在
Cookies
Name | Value |
---|---|
JSESSIONID | A58341FC3E4BD7A71700C4F65FF26AF3 |
模拟从数据库读取用户名密码,Demo 地址:mingyue-springboot-security
MingYueUserDetailsService
用户登录的逻辑处理处
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.csp.mingyue.security.model.MingYueUser;
import com.csp.mingyue.security.vo.LoginUserVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
/**
* @author Strive
* @date 2022/4/22 14:53
* @description
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MingYueUserDetailsService implements UserDetailsService {
private final MingYueUserService mingYueUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StrUtil.isBlank(username)) {
log.info("登录用户:{} 不存在", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
}
// 查出密码
MingYueUser user = mingYueUserService.queryUserByName(username);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
}
return new LoginUserVo(user);
}
}
MingYueUser
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/** @author Strive */
@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MingYueUser {
private Long userId;
private String username;
private String password;
}
LoginUserVo
import com.csp.mingyue.security.model.MingYueUser;
import java.util.Collection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* 登录用户信息
*
* @author Strive
* @date 2022/4/22 14:59
* @description
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class LoginUserVo implements UserDetails, CredentialsContainer {
/** 用户 */
private MingYueUser mingYueUser;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return new BCryptPasswordEncoder().encode(mingYueUser.getPassword());
}
@Override
public String getUsername() {
return mingYueUser.getUsername();
}
/** 账户是否未过期,过期无法验证 */
@Override
public boolean isAccountNonExpired() {
// true:未过期
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* 密码锁定
*/
@Override
public boolean isAccountNonLocked() {
// true:未锁定
return true;
}
/** 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 */
@Override
public boolean isCredentialsNonExpired() {
// true:未过期
return true;
}
/** 用户是否被启用或禁用。禁用的用户无法进行身份验证。 */
@Override
public boolean isEnabled() {
// true:未禁用
return true;
}
/** 认证完成后,擦除密码 */
@Override
public void eraseCredentials() {
mingYueUser.setPassword(null);
}
}
MingYueUserService
import com.csp.mingyue.security.model.MingYueUser;
import org.springframework.stereotype.Service;
/** @author Strive */
@Service
public class MingYueUserService {
/**
* 根据用户名查询用户信息
*
* @param username 用户名
* @return 用户信息
*/
public MingYueUser queryUserByName(String username) {
return MingYueUser.builder().userId(1L).username(username).password("123456").build();
}
}
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
/**
* @author Strive
* @date 2022/4/22 15:07
* @description
*/
@Slf4j
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(
HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException, ServletException {
log.info("login error");
if (e instanceof AccountExpiredException) {
// 账号过期
log.info("[登录失败] - 用户账号过期");
} else if (e instanceof BadCredentialsException) {
// 密码错误
log.info("[登录失败] - 用户密码错误");
} else if (e instanceof CredentialsExpiredException) {
// 密码过期
log.info("[登录失败] - 用户密码过期");
} else if (e instanceof DisabledException) {
// 用户被禁用
log.info("[登录失败] - 用户被禁用");
} else if (e instanceof LockedException) {
// 用户被锁定
log.info("[登录失败] - 用户被锁定");
} else {
// 其他错误
log.error(String.format("[登录失败] - [%s]其他错误"), e);
}
}
}
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
/**
* @author Strive
* @date 2022/4/22 15:05
* @description
*/
@Slf4j
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
log.info("login success");
}
}
import com.csp.mingyue.security.handler.LoginFailureHandler;
import com.csp.mingyue.security.handler.LoginSuccessHandler;
import com.csp.mingyue.security.service.MingYueUserDetailsService;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* Spring Security 配置
*
* @author Strive
* @date 2022/4/22 15:11
* @description
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final MingYueUserDetailsService mingYueUserDetailsService;
/** 登录成功的处理 */
private final LoginSuccessHandler loginSuccessHandler;
/** 登录失败的处理 */
private final LoginFailureHandler loginFailureHandler;
/** 配置认证方式等 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(mingYueUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
/** http相关的配置,包括登入登出、异常处理、会话管理等 */
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.authorizeRequests()
// 放行接口
// .antMatchers().permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest()
.authenticated()
// 登入
.and()
.formLogin()
// 允许所有用户
.permitAll()
// 登录成功处理逻辑
.successHandler(loginSuccessHandler)
// 登录失败处理逻辑
.failureHandler(loginFailureHandler);
}
}
地址 | 用户名 | 密码 |
---|---|---|
http://127.0.0.1:8080/login | mingyue | 123456 |
Spring Security 中文文档:
接口类 | 说明 |
---|---|
AuthenticationEntryPoint | 实现 AuthenticationEntryPoint 接口,可以处理匿名用户访问无权限资源时的异常 |
UserDetailsService | 用户登录认证逻辑 |
AuthenticationSuccessHandler | 登录成功的处理 |
AuthenticationFailureHandler | 登录失败的处理 |
LogoutSuccessHandler | 退出登录的回调 |
InvalidSessionStrategy | 登录超时的处理 |
SessionInformationExpiredStrategy | 同一账号同时登录的用户数受限的处理 |
AccessDeniedHandler | 登录用户没有权限访问的处理 |
HttpSessionIdResolver | 自定义 Session 解析器 |