介绍:
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。
它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
转 https://blog.youkuaiyun.com/yuanlaijike/category_9283872.html
重要代码:
登录相关:
我们需要重写 loadUserByUsername 方法,参数是用户输入的用户名。返回值是UserDetails,这是一个接口,一般使用它的子类org.springframework.security.core.userdetails.User,它有三个参数,分别是用户名、密码和权限集。
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private SysUserService userService;
@Autowired
private SysRoleService roleService;
@Autowired
private SysUserRoleService userRoleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection<GrantedAuthority> authorities = new ArrayList<>();
// 从数据库中取出用户信息
SysUser user = userService.selectByName(username);
// 判断用户是否存在
if(user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 添加权限
List<SysUserRole> userRoles = userRoleService.listByUserId(user.getId());
for (SysUserRole userRole : userRoles) {
SysRole role = roleService.selectById(userRole.getRoleId());
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
// 返回UserDetails实现类
return new User(user.getName(), user.getPassword(), authorities);
}
}
首先将我们自定义的userDetailsService
注入进来,在configure()
方法中使用auth.userDetailsService()
方法替换掉默认的 userDetailsService。
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 如果有允许匿名的url,填在下面
// .antMatchers().permitAll()
.anyRequest().authenticated()
.and()
// 设置登陆页
.formLogin().loginPage("/login")
// 设置登陆成功页
.defaultSuccessUrl("/").permitAll()
// 自定义登陆用户名和密码参数,默认为username和password
// .usernameParameter("username")
// .passwordParameter("password")
.and()
.logout().permitAll();
// 关闭CSRF跨域
http.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
}
授权:
@PreAuthorize("hasPermission('/admin','r')")
是关键,参数1指明了访问该接口需要的url,参数2指明了访问该接口需要的权限
@RequestMapping("/admin/c")
@ResponseBody
@PreAuthorize("hasPermission('/admin','c')")
public String printAdminC() {
return "如果你看见这句话,说明你访问/admin路径具有c权限";
}
在 hasPermission() 方法中,参数 1 代表用户的权限身份,参数 2 参数 3 分别和 @PreAuthorize("hasPermission('/admin','r')") 中的参数对应,即访问 url 和权限。
思路如下:
通过 Authentication 取出登录用户的所有 Role
遍历每一个 Role,获取到每个Role的所有 Permission
遍历每一个 Permission,只要有一个 Permission 的 url 和传入的url相同,且该 Permission 中包含传入的权限,返回 true
如果遍历都结束,还没有找到,返回false
import jit.wxs.entity.SysPermission;
import jit.wxs.service.SysPermissionService;
import jit.wxs.service.SysRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
private SysPermissionService permissionService;
@Autowired
private SysRoleService roleService;
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object targetPermission) {
// 获得loadUserByUsername()方法的结果
User user = (User)authentication.getPrincipal();
// 获得loadUserByUsername()中注入的角色
Collection<GrantedAuthority> authorities = user.getAuthorities();
// 遍历用户所有角色
for(GrantedAuthority authority : authorities) {
String roleName = authority.getAuthority();
Integer roleId = roleService.selectByName(roleName).getId();
// 得到角色所有的权限
List<SysPermission> permissionList = permissionService.listByRoleId(roleId);
// 遍历permissionList
for(SysPermission sysPermission : permissionList) {
// 获取权限集
List permissions = sysPermission.getPermissions();
// 如果访问的Url和权限用户符合的话,返回true
if(targetUrl.equals(sysPermission.getUrl())
&& permissions.contains(targetPermission)) {
return true;
}
}
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
return false;
}
}
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ... /** * 注入自定义PermissionEvaluator */ @Bean public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler(){ DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler(); handler.setPermissionEvaluator(new CustomPermissionEvaluator()); return handler; } ... }
Spring Security 原理学习笔记 --转
Authentication类:参与验证的要素(用户名、密码)在前端由表单提交,由网络传入后端后,会形成一个Authentication类的实例。该实例在进行验证前,携带了用户名、密码等信息;在验证成功后,则携带了身份信息、权限信息等。
要加入想自定义的验证功能,就是向ProviderManager中加入一个自定义的AuthenticationProvider实例。我们想要实现从数据库中读取用户名、密码,与前端输入的信息进行对比验证。为了加入使用数据库进行验证的DaoAuthenticationProvider类(这个类在我们的代码中是透明的)实例,可以使用AuthenticationManagerBuilder类的userDetailsService(UserDetailsService)方法 ,
例如:
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).而我们需要掌握的,就是由Security框架提供的两个接口UserDetails和UserDetailsService。其中UserDetails接口中定义了用于验证的“用户详细信息”所需的方法
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
。而UserDetailsService接口仅定义了一个方法loadUserByUsername(String username) 。这个方法由接口的实现类来具体实现,它的作用就是通过用户名username从数据库中查询,并将结果赋值给一个UserDetails的实现类实例。验证流程如下:
由于在上面的configure方法中调用了userDetailsService(customUserService)方法,因此在ProviderManager的验证链中加入了一个DaoAuthenticationProvider类的实例;
验证流程进行到DaoAuthenticationProvider时,它调用用户自定义的customUserService服务的loadUserByUsername方法,这个方法会从数据库中查询用户名是否存在;
若存在,则从数据库中返回的信息会组成一个UserDetails接口的实现类的实例,并将此实例返回给DaoAuthenticationProvider进行密码比对,比对成功则通过验证。
总结一下,鉴权过程就是在用户访问某资源链接时,首先读取资源表的记录,获取该资源链接的权限列表(若列表为空则说明该资源不需要额外的访问权限,任何用户都可访问)。再次,获取当前登录用户的权限信息(即Authentication类的getAuthorities())。最后,将两者进行匹配即可决定鉴权是否通过。