Spring Security是基于Spring生态圈的,用于提供安全访问解决方案的框架,主要有两大功能:可定制的身份验证和访问控制框架,主要有如下功能:
- Spring Security是Spring框架的一个模块,提供了全面的安全解决方案,包括身份认证、授权、攻击防护等功能。
- Spring Security构建在Spring框架之上,可以与Spring应用程序无缝集成,利用Spring的依赖注入和AOP等特性。
- Spring Security提供了大量的扩展点和配置选项,可以根据需求进行定制和扩展,例如使用自定义的身份验证提供程序、授权策略等。
- Spring Security在大型和复杂的应用程序中表现良好,特别是与其他Spring模块集成时。
以下是一种应用的实现方式:
引用pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
启动类添加启用注解,启用Spring Security
@EnableGlobalMethodSecurity(prePostEnabled = true)
实现userDetailsService接口
package com.huq.moms.service.impl;
import com.huq.moms.dao.PermissionDao;
import com.huq.moms.dto.LoginUser;
import com.huq.moms.model.SysPermission;
import com.huq.moms.model.SysUser;
import com.huq.moms.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
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.Service;
import java.util.List;
/**
* spring security登陆处理<br>
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private PermissionDao permissionDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = userService.getUser(username);
if (sysUser == null) {
throw new AuthenticationCredentialsNotFoundException("用户名不存在");
} else if (sysUser.getStatus() == SysUser.Status.LOCKED) {
throw new LockedException("用户被锁定,请联系管理员");
} else if (sysUser.getStatus() == SysUser.Status.DISABLED) {
throw new DisabledException("用户已作废");
}
LoginUser loginUser = new LoginUser();
BeanUtils.copyProperties(sysUser, loginUser);
//用户登录权限相关信息放入 loginUser
List<SysPermission> permissions = permissionDao.listByUserId(sysUser.getId());
loginUser.setPermissions(permissions);
return loginUser;
}
}
loginUser 实现需要集成UserDetails接口
package com.huq.moms.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.huq.moms.model.SysPermission;
import com.huq.moms.model.SysUser;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Data
public class LoginUser extends SysUser implements UserDetails {
private static final long serialVersionUID = -1379274258881257107L;
private List<SysPermission> permissions;
private String token;
/** 登陆时间戳(毫秒) */
private Long loginTime;
/** 过期时间戳 */
private Long expireTime;
//权限数据存储,用于匹配用户是否有权限
@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
return permissions.parallelStream().filter(p -> !StringUtils.isEmpty(p.getPermission()))
.map(p -> new SimpleGrantedAuthority(p.getPermission())).collect(Collectors.toSet());
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
// do nothing
}
// 账户是否未过期
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
// 账户是否未锁定
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return getStatus() != Status.LOCKED;
}
// 密码是否未过期
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 账户是否激活
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
public Long getLoginTime() {
return loginTime;
}
public void setLoginTime(Long loginTime) {
this.loginTime = loginTime;
}
public Long getExpireTime() {
return expireTime;
}
public void setExpireTime(Long expireTime) {
this.expireTime = expireTime;
}
}
继承WebSecurityConfigurerAdapter,配置规则相关
package com.huq.moms.security;
import com.huq.moms.security.authentication.MyAuthenctiationFailureHandler;
import com.huq.moms.security.authentication.MyAuthenticationSuccessHandler;
import com.huq.moms.security.authentication.MyLogoutSuccessHandler;
import com.huq.moms.security.authentication.RestAuthenticationAccessDeniedHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
// 登录成功handler
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
// 登录失败handler
@Autowired
private MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;
// 资源禁止访问handler
@Autowired
private RestAuthenticationAccessDeniedHandler restAuthenticationAccessDeniedHandler;
@Autowired
private MyLogoutSuccessHandler myLogoutSuccessHandler;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable();
httpSecurity.authorizeRequests()
.antMatchers("/login.html",
"/my/**",
"/treetable-lay/**",
"/xadmin/**",
"/ztree/**",
"/statics/**"
)
.permitAll()
.anyRequest()
.authenticated()
;
//解决X-Frame-Options DENY问题
httpSecurity.headers().frameOptions().sameOrigin();
httpSecurity.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenctiationFailureHandler)
.and().logout().permitAll().invalidateHttpSession(true).
deleteCookies("JSESSIONID").logoutSuccessHandler(myLogoutSuccessHandler)
;
//异常处理
httpSecurity.exceptionHandling().accessDeniedHandler(restAuthenticationAccessDeniedHandler);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
常用认证注解
- @Secured :方法执行前检查,直接判断有没有对应的角色
- @PreAuthorize:方法执行前检查,根据SpEL表达式执行结果判断是否授权
- @PostAuthorize:方法执行后检查,根据SpEL表达式执行结果判断是否授权
示例(sys:menu:del对应LoginUser方法中getAuthorities()中集合权限数据,在权限矩阵中配置):
@RequestMapping(value = "/delete", method = RequestMethod.GET)
@ResponseBody
@PreAuthorize("hasAuthority('sys:menu:del')")
@ApiOperation(value = "删除菜单", notes = "根据菜单Id去删除菜单")//描述
public Results deletePermission(SysPermission sysPermission) {
return permissionService.delete(sysPermission.getId());
}
Spring Security认证步骤
- 自定UserDetails类:当实体对象字段不淌足时需要自定义UserDetails,一般都要自定义UserDetails。
- 自定义UserDetailsService类,主要用于从数据库查询用户信息。
- 创建登录认证成功处理器,认证成功后需要返回JSON数据,菜单权限等。
- 创建登录认证失败处理器,认证失败需要返回JSON数据,给前端判断。
- 创建匿名用户访问无权限资源时处理器,匿名用户访问时.需要提示JSON.
- 创建认证过的用户访问无权限资源时的处理器,无权限访问时,需要提示JSON。
- 配置Spring Security配置类,把上面自定义的处理器交给Spring Security.