美文网首页Spring Security
Spring Security安全认证服务框架

Spring Security安全认证服务框架

作者: 月哥说了算 | 来源:发表于2019-07-26 13:11 被阅读0次

Spring Security是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我们来简化认证和授权的过程。常用的权限框架除了Spring Security,还有Apache的shiro框架。

官网:https://spring.io/projects/spring-security

Spring-Security认证流程

用户一次完整的登录验证和授权,是一个请求经过 层层拦截器从而实现权限控制,整个web端配置为DelegatingFilterProxy(springSecurity的委托过滤其代理类 ),它并不实现真正的过滤,而是所有过滤器链的代理类,真正执行拦截处理的是由spring 容器管理的个个filter bean组成的filterChain.

拦截
1560471347271.png
认证与授权

认证(Authentication):确定一个用户的身份的过程。授权(Authorization):判断一个用户是否有访问某个安全对象的权限。下面讨论一下spring security中最基本的认证与授权。

首先明确一下在认证与授权中关键的是UsernamePasswordAuthenticationFilter:该过滤器用于拦截我们表单提交的请求(默认为/login),进行用户的认证过程。


1560471452357.png

授权登录的请求,UsernamePasswordAuthenticationFilter将拦截请求进行认证。


1560471461308.png
AuthenticationProvider中维护了UserDetailsService,我们使用内存中的用户,默认的实现是InMemoryUserDetailsManager。UserDetailsService用来查询用户的详细信息,该详细信息就是UserDetails。UserDetails的默认实现是User。查询出来UserDetails后再对用户输入的密码进行校验。校验成功则将UserDetails中的信息填充进Authentication中返回。校验失败则提醒用户密码错误。

使用:

对应的maven坐标:

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-web</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-config</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>

配置spring-security.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

    <!--1.配置不需要授权访问的资源-->
    <security:http security="none" pattern="/favicon.ico"/>
    
    <!--
        http:security:权限设置,none,排除资源
        http:auto-config:是否自动配置
                     设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出处理等
                     设置为false时需要显示提供登录表单配置,否则会报错
              use-expressions:用于指定intercept-url中的access属性是否使用表达式(SpEL表达式)
      -->
    <!--1.配置不需要授权访问的资源-->
    <security:http auto-config="true" use-expressions="true">
        <!--
            intercept-url:定义一个拦截规则
            pattern:对哪些url进行权限控制
            access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
                  请求的用户只需拥有其中的一个角色就能成功访问对应的URL
        -->
        <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN')"/>
    </security:http>
    <!-- 3.配置认证授权管理器(认证管理者、认证提供者、认证对象)-->
    <security:authentication-manager>
        <!--
            authentication-provider:认证提供者,执行具体的认证逻辑
        -->
        <security:authentication-provider>
            <!--
               user-service:用于获取用户信息,提供给authentication-provider进行认证
             -->
            <security:user-service>
                <security:user name="admin" password="{noop}123" authorities="ROLE_ADMIN"/>
            </security:user-service>
        </security:authentication-provider>

    </security:authentication-manager>

</beans>
Snipaste_2019-07-26_10-45-53.png

授权验证业务类UserDetailsService

如果我们要从数据库动态查询用户信息,就必须按照spring security框架的要求提供一个实现UserDetailsService接口的实现类,并按照框架的要求进行配置即可。框架会自动调用实现类中的方法并自动进行密码校验。

一般实现内容:

  1. 根据用户标识(用户名),从数据库读取用户信息
  2. 提取用户信息的角色与权限关键词信息
  3. 把用户角色与权限关键词封装为List<GrantedAuthority>列表
  4. 构建UserDetail对象(使用Security框架自动的User类封装),封装用户名、密码(必须是加密过的)及权限角色关键词列表
    实现:
package com.gzy.health.security;

import com.gzy.health.pojo.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author :gzy
 * @date :Created in 2019/7/26
 * @description :
 * @version: 1.0
 */

public class SecurityUserDetailsService implements UserDetailsService {
    // 模拟数据库的用户记录,如下User类是health_common中的自定义实体类User
    // 修改Role、Permission,为其增加不带参、带参构造方法
    private static Map<String, User> userDb = new HashMap();
    @Autowired
    private BCryptPasswordEncoder encoder;//引入加密对象
    static {
        User user1 = new User();
        user1.setUsername("admin");
        user1.setPassword("123");
        // 用户权限与角色
        Role role1 = new Role("系统管理员","ROLE_ADMIN");
        role1.getPermissions().add(new Permission("添加权限","add"));
        role1.getPermissions().add(new Permission("删除权限","delete"));
        role1.getPermissions().add(new Permission("更新权限","update"));
        role1.getPermissions().add(new Permission("查询权限","find"));
        user1.getRoles().add(role1);
        userDb.put(user1.getUsername(),user1);

        User userZhangSan = new User();
        userZhangSan.setUsername("zhangsan");
        userZhangSan.setPassword("123");
        Role role2 = new Role("数据分析员","ROLE_READER");;
        role2.getPermissions().add(new Permission("查询权限","find"));
        userZhangSan.getRoles().add(role2);
        userDb.put(userZhangSan.getUsername(),userZhangSan);

        User userLisi = new User();
        userLisi.setUsername("lisi");
        userLisi.setPassword("123");
        Role role3 = new Role("运营管理员","ROLE_OMS");;
        role3.getPermissions().add(new Permission("添加权限","add"));
        role3.getPermissions().add(new Permission("更新权限","update"));
        userLisi.getRoles().add(role3);
        userDb.put(userLisi.getUsername(),userLisi);
    }
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User user = userDb.get(s);
        if(user==null){
            return null;
        }
        List<GrantedAuthority> authList=new ArrayList<>();
        for (Role role : user.getRoles()) {
            authList.add(new SimpleGrantedAuthority(role.getKeyword()));
            for (Permission permission : role.getPermissions()) {
                authList.add(new SimpleGrantedAuthority(permission.getKeyword()));
            }
        }
        // 如果密码未加密,必须用默认加密规则加密
        // 如果密码已加密,则不用使用默认规则加密
        String passwordInDb = user.getPassword();
        //对密码加密
        String authPassword = encoder.encode(passwordInDb);
        UserDetails userDetails =
                new org.springframework.security.core.userdetails.User(user.getUsername(),authPassword,authList);
        return userDetails;
    }
}

这里用了bcrypt对密码加密。

对spring-security.xml的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

    <!--1.配置不需要授权访问的资源-->
    <security:http security="none" pattern="/favicon.ico"/>
    <security:http security="none" pattern="/login.jsp"/>
    <security:http security="none" pattern="/login-fail.jsp"/>
    <security:http security="none" pattern="/auth-fail.jsp"/>
    <!--
        http:security:权限设置,none,排除资源
        http:auto-config:是否自动配置
                     设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出处理等
                     设置为false时需要显示提供登录表单配置,否则会报错
              use-expressions:用于指定intercept-url中的access属性是否使用表达式(SpEL表达式)
      -->
    <!--1.配置不需要授权访问的资源-->
    <security:http auto-config="true" use-expressions="true">
        <!--
            intercept-url:定义一个拦截规则
            pattern:对哪些url进行权限控制
            access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
                  请求的用户只需拥有其中的一个角色就能成功访问对应的URL
        -->
        <!--<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN')"/>-->
        <!--配置三种角色,都可以访问的资源-->
        <security:intercept-url pattern="/main.jsp" access="hasAnyRole('ROLE_ADMIN','ROLE_READER','ROLE_OMS')"/>
        <!--有find权限,可以访问该资源-->
        <security:intercept-url pattern="/pages/checkitem.html" access="hasAuthority('find')"/>
        <!--有add、delete权限,可以访问该资源-->
        <security:intercept-url pattern="/pages/checkgroup.html" access="hasAnyAuthority('add','update')"/>
        <!---自定义登录配置-->
        <security:form-login
                login-page="/login.jsp"
                default-target-url="/main.jsp"
                login-processing-url="/login.do"
                authentication-failure-url="/login-fail.jsp"
        />
        <!--访问拒绝处理器-->
        <security:access-denied-handler error-page="/auth-fail.jsp"/>
 <!--
  logout:退出登录
  logout-url:退出登录操作对应的请求路径
  logout-success-url:退出登录后的跳转页面
-->
        <security:logout logout-url="/logout.do"
                         logout-success-url="/login.jsp" invalidate-session="true"/>
        <!--
           csrf:对应CsrfFilter过滤器
           disabled:如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)
          -->
        <security:csrf disabled="true"/>
    </security:http>
    <!-- 3.配置认证授权管理器(认证管理者、认证提供者、认证对象)-->
    <security:authentication-manager>
        <!--
            authentication-provider:认证提供者,执行具体的认证逻辑
        -->
        <security:authentication-provider user-service-ref="securityUserDetailsService">
            <!--
               user-service:用于获取用户信息,提供给authentication-provider进行认证
             -->
            <!--<security:user-service>-->
                <!--<security:user name="admin" authorities="ROLE_ADMIN" password="{noop}123"/>-->
                <!--<security:user name="zhangsan" authorities="ROLE_READER,find" password="{noop}123"/>-->
                <!--<security:user name="lisi" authorities="ROLE_OMS,add,update" password="{noop}123"/>-->
            <!--</security:user-service>-->
            <!--密码 加密方式-->
            <security:password-encoder ref="passwordEncoder"/>
        </security:authentication-provider>

    </security:authentication-manager>
    <!--开启注解方式权限控制-->
    <security:global-method-security pre-post-annotations="enabled" />
    <!-- 配置自定义认证对象-->
    <bean id="securityUserDetailsService" class="com.gzy.health.security.SecurityUserDetailsService"/>
    <!--配置密码加密对象-->
    <bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="passwordEncoder"/>
</beans>

使用注解方式控制Controller方法访问

Spring Security除了可以在配置文件中配置权限校验规则,还可以使用注解方式控制类中方法的调用。例如Controller中的某个方法要求必须具有某个权限才可以访问,此时就可以使用Spring Security框架提供的注解方式进行控制。

实现步骤:

第一步:在spring-mvc.xml文件中配置组件扫描及注解驱动

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/tool/spring-mvc.xsd">
    <context:component-scan base-package="com.itheima.health"/>
    <mvc:annotation-driven/>
</beans>

第二步:在spring-security.xml文件中开启权限注解支持

<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />

第三步:修改TestSecurityController类并在Controller的方法上加入注解进行权限控制

@RestController
@RequestMapping("/test")
public class TestSecurityController {

    @PreAuthorize("hasAuthority('add')")
    @RequestMapping("/addData")
    public String addData(){
        return "add ok";
    }

    @PreAuthorize("hasAuthority('update')")
    @RequestMapping("/updateData")
    public String updateData(){
        return "update ok";
    }

    @PreAuthorize("hasAuthority('delete')")
    @RequestMapping("/delData")
    public String deleteData(){
        return "delete ok";
    }

    @PreAuthorize("hasAuthority('find')")
    @RequestMapping("/findData")
    public String findAll(){
        return "find ok";
    }
}

相关文章

网友评论

    本文标题:Spring Security安全认证服务框架

    本文链接:https://www.haomeiwen.com/subject/atkrrctx.html