05.SpringSecurity

SpringSecurity

一、概述

Spring Security的前身是Acegi Security ,是Spring项目中用来提供安全认证服务的框架,为基于J2EE企业应用软件提供了全面安全服务。

Spring Security 底层就是实现了一个 过滤器 接口

1). 关键名词

  1. 认证:是为用户建立一个他所声明的主体。主题一般式指用户,设备或可以在你系 统中执行动作的其他系统
  2. 授权:指的是一个用户能否在你的应用中执行某个操作,在到达授权判断之前,身份的主题已经由 身份验证过程建立了。

二、基本配置,用户登陆认证

1). maven坐标依赖


    org.springframework.security
    spring-security-web
    5.0.1.RELEASE


    org.springframework.security
    spring-security-config
    5.0.1.RELEASE

2). 配置web.xml

...


    org.springframework.web.context.ContextLoaderListener



    contextConfigLocation
    classpath*:applicationContext.xml,classpath*:springSecurity.xml




    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy


    springSecurityFilterChain
    /*

...

3). 配置springSecurity.xml文件




    
    
    
    
    
    

    
    

        
        

        
        

        
        

        
        

        
        
            
        

    

    
    
        
            
            

        
    

    
    


    

4). UserService接口的继承接口实现类

import org.springframework.security.core.userdetails.UserDetailsService;
public interface UserService extends UserDetailsService {}
@Service("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) {

        // userInfo 类包含了用户信息,以及用户关联的角色信息
        UserInfo userInfo = userDao.findByUsername(username);

        // 获取角色信息
        List roles = userInfo.getRoles();

        // 将角色信息封装到框架指定的类型容器中
        List authorities = getAuthority(roles);

        /** 【注意】:下方使用的User类是框架提供的!将上面查询的用户信息,角色信息封装到框架要求的容器中!
         org.springframework.security.core.userdetails.User
         是UserDetails的实现类,Spring Security 框架从数据库中查询认证信息
         调用的就是这个接口中的方法,既然是框架作者规定好的,开发者照着规则
         将信息封装到这个接口实现类中即可。。。本例使用的构造函数如下:
         public User(
                String username, 用户名
                String password, 密码(如果密码没有加密,需要前缀识别,"{noop}")
                boolean enabled, 用户是否可用?true表示能认证,反之不能!
                boolean accountNonExpired, 账户没有过期?
                boolean credentialsNonExpired, 证书没有过期?
                boolean accountNonLocked, 账户没有被锁?
                Collection authorities 当前用户拥有的角色
                         GrantedAuthority 接口提供 getAuthority 抽象方法;框架提供了一个实现类
                         SimpleGrantedAuthority;将查询的角色封装到此对象,
                          然后丢给Spring Security 框架 就可以了
         ) */
        User user = new User(
                userInfo.getUsername(),
                userInfo.getPassword(),
                userInfo.getStatus() != 0,
                true, true, true,
                authorities);
        return user;
    }

    /** 封装 角色 到Spring Security框架提供的实现类中! */
    private List getAuthority(List roles) {
        List authorities = new ArrayList<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName()));
        }

        return authorities;
    }
}
  • 补充:UserDetails 接口源码
public interface UserDetails extends Serializable {
    Collection getAuthorities();
    String getPassword();
    String getUsername();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
}
  • 补充:UserDetails接口的实现类User 部分源码
public class User implements UserDetails, CredentialsContainer {
    private String password;
    private final String username;
    private final Set authorities;
    private final boolean accountNonExpired; //帐户是否过期
    private final boolean accountNonLocked; //帐户是否锁定
    private final boolean credentialsNonExpired; //认证是否过期
    private final boolean enabled; //帐户是否可用
    ...
}

5). dao层代码

查询users表 以及多对多关联的 role 表,这个不是本文章描述的重点,用法和常规的dao层无异!

6). userServiceImpl新增用户的密码处理

/** 框架提供的加密工具类,每次加密底层都会产生一个动态的“盐”
* 每次加密的结果都不同,比md5加密方式更加安全;
* 虽然每次加密都不同,可以根据头信息计算出动态的盐,然后再进行计算比较(不是解密过程)*/
@Autowired
private BCryptPasswordEncoder passwordEncoder;

/** 插入一条用户信息,密码需要进行加密 */
@Override
public Integer saveUser(UserInfo userInfo) {
    userInfo.setPassword(passwordEncoder.encode(userInfo.getPassword()));
    return userDao.saveUser(userInfo);
}

二、服务器URI端动态权限控制【适合复杂的权限级别控制】

Spring Security框架提供了接口,在标签 中使用特定SPEL即可实现

1). springSecurity.xml配置文件的配置变化

...




    
    
...

2). 上面SPEL表达式需要调用的接口

public interface PermissionService {
    ...

    /** 动态权限验证 */
    public Boolean hasPermission(HttpServletRequest request, Authentication authentication);
}
@Component("permissionService")
public class PermissionServiceImpl implements PermissionService {

    ...

    /** 动态权限验证 */
    @Override
    public Boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        // 拿到域中的用户信息对象!
        Object principal = authentication.getPrincipal();

        // 要求为Spring Security框架提供的 User 接口实现类 UserDetails
        if(principal instanceof UserDetails) {

            // 获取 用户名 信息、注意,是用户名,不是角色名
            String username = ((UserDetails)principal).getUsername();

            // 当前用户可访问的所有的url
            Set urls = getUserPermission(username);
            if (urls.contains(request.getRequestURI())) {
                return true;
            }
        }
        return false;
    }

    /**
     模拟从数据库中根据对应的【username】拿到该用户
     的角色,以及角色拥有的访问权限的【uri】;实际项目中,
     需要将这些信息放在【redis】缓存中,查询时在redis中查找当对权限进行 增删改 时
     需要同时维护 redis中的对应数据!这里只是简单的模拟一下!
     */
    private Set getUserPermission(String username) {

        // 存放用户角色以及对应的uri
        Map> map = new HashMap<>();

        // 006 角色
        Set set006 = new HashSet<>();
        set006.add("/product/findAll");

        // 007 角色
        Set set007 = new HashSet<>();
        set007.add("/order/findAll");

        // 将角色信息加入map中
        map.put("006", set006);
        map.put("007", set007);

        // 根据username 获取对应的 信息
        Set urls = map.get(username);

        // 防止空指针异常
        return urls == null ? new HashSet<>() : urls;
    }
}

三、服务器端URI方法级权限控制【适合权限控制单一的场景】

在服务器端我们可以通过Spring security提供的注解对方法来进行权限控制。Spring Security在方法的权限控制上支持三种类型的注解,JSR-250注解、@Secured注解和支持表达式的注解,这三种注解默认都是没有启用的,需要单独通过global-method-security元素的对应属性进行启用

  1. 配置文件方式开启注解


  1. (了解) 注解配置的Spring security框架的开启方式

@EnableGlobalMethodSecurity :Spring Security默认是禁用注解的,要想开启注解,需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解,并在该类中将AuthenticationManager定义为Bean。

1). JSR-250注解的使用

  1. maven坐标

    javax.annotation
    jsr250-api
    1.0

  1. Spring Security 配置文件springSecurity.xml 注解开启


  1. 在需要被 过滤 的Controller中的方法 上添加注解 @RolesAllowed({"ADMIN"})

注解中的参数value是一个数组,可以省略 前缀 "ROLE_";当登陆的用户拥有列表中的权限,访问有效;
在之前用户登陆的时候,用户的认证信息是按照框架的接口规则将数据封装,框架会将当前的用户相关信息封装到session域中!
通过此session域,可以在过滤器(或者拦截器)中进行权限判断过滤!

@RolesAllowed({"ADMIN"})
@RequestMapping("/findAll")
public @ResponseBody ResponseMsg findAll() {...}
  1. 配置web.xml 403 forbidden


    403
    /error/403

  1. 编写 Controller
@Controller
@RequestMapping("/error")
public class ErrorController {

    @RequestMapping("/403")
    public @ResponseBody ResponseMsg forbidden(){...}
    ...
  1. 补充(了解)
    1. @PermitAll: 表示允许所有的角色进行访问,也就是说不进行权限控制
    2. @DenyAll: 和PermitAll相反的,表示无论什么角色都不能访问

2). @Secured注解

使用方式与 JSR-250注解 类似,不过 @Secured 是 Spring Security框架提供的,不需要导入额外的maven坐标;
@Secured注解 不能省略前缀 "ROLE_"

  1. springSecurity.xml配置文件开启@Secured注解

  1. 在 Controller 的方法上使用注解
@Secured({"ROLE_ADMIN"})
@RequestMapping("/findAll")
public @ResponseBody ResponseMsg findAll(...)
  1. 其他步骤与JSR-250注解一样

3). 支持表达式的注解

同样是内置的,不需要额外导包;表达式 很多,需要时问度娘即可;

  1. 配置开启 springSecurity.xml

  1. @PreAuthorize 使用举例
  • 需要访问者拥有的权限
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping("/findAll")
public @ResponseBody ResponseMsg findAll(...)
  • 指定特定用户能访问
@PreAuthorize("authentication.principal.username = '007'")
@RequestMapping("/saveProduct")
public @ResponseBody ResponseMsg saveProduct(Product product) {...}
  • 组合表达式
@PreAuthorize("#userId == authentication.principal.userId or hasAuthority(‘ADMIN’)")
void changePassword(@P("userId") long userId ){...}

表示在changePassword方法执行之前,判断方法参数userId的值是否等于principal中保存的当前用户的
userId,或者当前用户是否具有ROLE_ADMIN权限,两种符合其一,就可以访问该方法。
  1. 补充
  • @PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常
@PostAuthorize
User getUser("returnObject.userId == authentication.principal.userId or
hasPermission(returnObject, 'ADMIN')");
  • @PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果

  • @PreFilter 允许方法调用,但必须在进入方法之前过滤输入值

  • Spring Security允许我们在定义URL访问或方法访问所应有的权限时使用Spring EL表达式,在定义所需的访问权限时如果对应的表达式返回结果为true则表示拥有对应的权限,反之则无。Spring Security可用表达式对象的基类是SecurityExpressionRoot,其为我们提供了如下在使用Spring EL表达式对URL或方法进行权限控制时通用的内置表达式。

表达式 描述
hasRole([role]) 当前用户是否拥有指定角色。
hasAnyRole([role1,role2]) 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
hasAuthority([auth]) 等同于hasRole
hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole
Principle 代表当前用户的principle对象
authentication 直接从SecurityContext获取的当前Authentication对象
permitAll 总是返回true,表示允许所有的
denyAll 总是返回false,表示拒绝所有的
isAnonymous() 当前用户是否是一个匿名用户
isRememberMe() 表示当前用户是否是通过Remember-Me自动登录的
isAuthenticated() 表示当前用户是否已经登录认证成功了。
isFullyAuthenticated() 如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。

四、页面端标签控制权限(了解)

在jsp页面中我们可以使用spring security提供的权限标签来进行权限控制;相关的表达式可以去度娘学习参考

1). 准备

  1. 注意:需要在 springSecurity.xml中配置bean

  1. 导入maven坐标

    org.springframework.security
    spring-security-taglibs
    {version}

  1. jsp页面导入对应的taglib
<%@taglib uri="http://www.springframework.org/security/tags" prefix="security"%>

1). 三种标签使用介绍

在jsp中我们可以使用以下三种标签,其中authentication代表的是当前认证对象,可以获取当前认证对象信息,例如用户名。其它两个标签我们可以用于权限控制

1. authentication


  1. property:只允许指定Authentication所拥有的属性,可以进行属性的级联获取,如 principle.username,不允许直接通过方法进行调用
  2. htmlEscape:表示是否需要将html进行转义。默认为true。
  3. scope:与var属性一起使用,用于指定存放获取的结果的属性名的作用范围,默认pageContext。Jsp中拥有的作用范围都进行进行指定
  4. var: 用于指定一个属性名,这样当获取到了authentication的相关信息后会将其以var指定的属性名进行存放,默认是存放在pageConext中

2. authorize

authorize是用来判断普通权限的,通过判断用户是否具有对应的权限而控制其所包含内容的显示

...
  1. access: 需要使用表达式来判断权限,当表达式的返回结果为true时表示拥有对应的权限
  2. method:method属性是配合url属性一起使用的,表示用户应当具有指定url指定method访问的权限,method的默认值为GET,可选值为http请求的7种方法
  3. url:url表示如果用户拥有访问指定url的权限即表示可以显示authorize标签包含的内容
  4. var:用于指定将权限鉴定的结果存放在pageContext的哪个属性中

3. accesscontrollist

accesscontrollist标签是用于鉴定ACL权限的。其一共定义了三个属性:hasPermission、domainObject和var,其中前两个是必须指定的


  1. hasPermission:hasPermission属性用于指定以逗号分隔的权限列表
  2. domainObject:domainObject用于指定对应的域对象
  3. var:var则是用以将鉴定的结果以指定的属性名存入pageContext中,以供同一页面的其它地方使用

五、补充

1). 配置文件中将sercurity配置设置为默认




    


2). 密码修改业务中需要使用的方法

验证密码是否正确

BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
boolean matches = passwordEncoder.matches("原始密码", "被加密后的密码");

3). IS_AUTHENTICATED_ANONYMOUSLY匿名用户

access="IS_AUTHENTICATED_ANONYMOUSLY" 用于设置资源可以在不登陆时可以访问。此配置与 security="none"的区别在于当用户未登陆时获取登陆人账号的值为anonymousUser ,而security="none"的话,无论是否登陆都不能获取登录人账号的值。

...
 
        
        
        
...

你可能感兴趣的:(05.SpringSecurity)