Spring Security 使用及部分源码分析

0x01.Spring Security 概述

  • Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架。

  • 官网:(https://projects.spring.io/spring-security/)

  • Spring Security 为基于J2EE企业应用软件提供了全面安全服务。特别是使用领先的J2EE解决方案-Spring框架开发的企业软件项目。人们使用Spring Security有很多种原因,不过通常吸引他们的是在J2EE Servlet规范或EJB规范中找不到典型企业应用场景的解决方案。 特别要指出的是他们不能再WAR 或 EAR 级别进行移植。这样,如果你更换服务器环境,就要,在新的目标环境进行大量的工作,对你的应用系统进行重新配 置安全。使用Spring Security 解决了这些问题,也为你提供很多有用的,完全可以指定的其他安全特性。

  • 安全包括两个主要操作。

    • “认证”,是为用户建立一个他所声明的主体。主题一般式指用户,设备或可以在你系 统中执行动作的其他系统。
    • “授权”指的是一个用户能否在你的应用中执行某个操作,在到达授权判断之前,身份的主题已经由 身份验证过程建立了。
  • 在身份验证层面,Spring Security广泛支持各种身份验证模式,这些验证模型绝大多数都由第三方提供,或则正在开发的有关标准机构提供的,例如 Internet Engineering Task Force.作为补充,Spring Security 也提供了自己的一套验证功能。

  • Spring Security 目前支持认证一体化如下认证技术: HTTP BASIC authentication headers (一个基于IEFT RFC 的标准) HTTP Digest authentication headers (一个基于IEFT RFC 的标准) HTTP X.509 client certificate exchange (一个基于IEFT RFC 的标准) LDAP (一个非常常见的跨平台认证需要做法,特别是在大环境) Form-based authentication (提供简单用户接口的需求) OpenID authentication Computer Associates Siteminder JA-SIG Central Authentication Service (CAS,这是一个流行的开源单点登录系统) Transparent authentication context propagation for Remote Method Invocation and HttpInvoker (一个Spring远程调用协议)。

0x02.依赖

    <properties>
        <spring.security.version>5.0.1.RELEASE</spring.security.version>
    </properties>
		<dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>${spring.security.version}</version>
        </dependency>

0x03.具体使用

1.在web.xml中配置Spring Security的核心过滤器

  <!--spring security配置文件-->
  <display-name>SpringSecurity314</display-name>
  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  </welcome-file-list>
  • 同时需要制定核心配置文件spring-security的所在路径。
  <!--指定spring配置文件的路径-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:applicationContext.xml,classpath*:spring-security.xml</param-value>
  </context-param>

2.配置spring-security.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

    <!-- 配置不拦截的资源 -->
    <security:http pattern="/login.jsp" security="none"/>
    <security:http pattern="/failer.jsp" security="none"/>
    <security:http pattern="/css/**" security="none"/>
    <security:http pattern="/img/**" security="none"/>
    <security:http pattern="/plugins/**" security="none"/>

    <!--
    	配置具体的规则
    	auto-config="true"	不用自己编写登录的页面,框架提供默认登录页面
    	use-expressions="false"	是否使用SPEL表达式(没学习过)
    -->
    <security:http auto-config="true" use-expressions="false">
        <!-- 配置具体的拦截的规则 pattern="请求路径的规则" access="访问系统的人,必须有ROLE_USER的角色" -->
        <security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN"/>

        <!-- 定义跳转的具体的页面 -->
        <security:form-login
                login-page="/login.jsp"
                login-processing-url="/login.do"
                default-target-url="/index.jsp"
                authentication-failure-url="/failer.jsp"
                authentication-success-forward-url="/pages/main.jsp"
        />

        <!-- 关闭跨域请求 -->
        <security:csrf disabled="true"/>

        <!-- 退出 -->
        <security:logout invalidate-session="true" logout-url="/logout.do" logout-success-url="/login.jsp" />

    </security:http>

    <!-- 切换成数据库中的用户名和密码 -->
    <security:authentication-manager>
    	<!--配置相关的service-->
        <security:authentication-provider user-service-ref="userService">
            <!-- 配置加密的方式 -->
<!--            <security:password-encoder ref="passwordEncoder"/>-->
        </security:authentication-provider>
    </security:authentication-manager>

    <!-- 配置加密类 -->
    <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>



</beans>

3.编写serive相关接口和实现类

  • service接口需要扩展UserDetailsService
public interface UserService extends UserDetailsService {

}

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo userInfo=null;
        try {
            userInfo=userDao.findByUsername(username);

        } catch (Exception e) {
            e.printStackTrace();
        }
        User user= new User(userInfo.getUsername(),"{noop}"+userInfo.getPassword(),userInfo.getStatus()==0?false:true,true,true,true,getAuthority(userInfo.getRoles()));
        return user;
    }

    private List<SimpleGrantedAuthority> getAuthority(List<Role> roles) {
        List<SimpleGrantedAuthority>  list=new ArrayList<>();
        for(Role role:roles){
            list.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleName()));
        }

        return list;
    }
}

4.实体类:

public class UserInfo {
    private String id;
    private String username;
    private String email;
    private String password;
    private String phoneNum;
    private int status;
    private String statusStr;
    private List<Role> roles;

    // getters and setters

}
public class Role {
    private String id;
    private String roleName;
    private String roleDesc;
    private List<Permission> permissions;
    private List<User> users;

    //getters and setters

   
}
public class Permission {
    private String id;
    private String permissionName;
    private String url;
    private List<Role> roles;

   //getters and setters


 
}

5.dao层(已配置MyBatis)

public interface UserDao {
    @Select("select * from users where username=#{username}")
    @Results({
            @Result(id=true,property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            @Result(property = "email",column = "email"),
            @Result(property = "password",column = "password"),
            @Result(property = "username",column = "username"),
            @Result(property = "phoneNum",column = "phoneNum"),
            @Result(property = "status",column = "status"),
            @Result(property = "roles",column = "id",javaType = java.util.List.class,
            many=@Many(select="com.atfwus.dao.RoleDao.findRoleByUserId"))

    })
    public UserInfo findByUsername(String Username) throws Exception;


}
public interface RoleDao {
    @Select("select * from role where id in
    (select roleId from users_role where userId=#{userId})")
    public List<Role> findRoleByUserId(String userId) throws Exception;
}

细节部分:

  • 简单使用可以暂时把加密配置去掉,不然与数据库的数据不匹配。
  • 不适用加密的话,在密码传参的时候应该在密码前面加上{noop},比如"{noop}"+userInfo.getPassword()
  • 配置文件中已加入退出的相关配置,只需要访问/logout.do即可退出。

0x04.部分源码分析

1.springSecurityFilterChain

  • web.xml文件中配置的过滤器是springSecurityFilterChain,真正执行的类是DelegatingFilterProxy

在这里插入图片描述

  • 查看DelegatingFilterProxy类,发现继承了一个父类GenericFilterBean

在这里插入图片描述

  • 查看GenericFilterBean类,发现它是一个filter,说明DelegatingFilterProxy也是一个filter

在这里插入图片描述

  • filter真正起作用的是doFilter方法,查看DelegatingFilterProxydoFilter方法。
    在这里插入图片描述在这里插入图片描述

  • 我们能够得知,真正在发挥作用的还是这个Filterdelegate。在下面可以发现有两个方法对delegate进行了初始化:initFilterBean,initDelegate

  • 查看initFilterBean:
    在这里插入图片描述

  • 查看是如何获取名字的:
    在这里插入图片描述

  • 查看initDelegate:
    在这里插入图片描述

  • 到这我们可以发现,最终起作用的delegate其实就是我们在web,xml中配置的名为springSecurityFilterChain的一个bean,事实上就是FilterChainProxy

  • 所以在在配置核心过滤器的时候,filter的名字一定得是springSecurityFilterChain

2.spring-security配置文件

  • 在源码的spring.handers中,可以发现具体由SecurityNamespaceHandler解析名称空间。
    在这里插入图片描述
  • 查看SecurityNamespaceHandler

在这里插入图片描述- 查看init方法:

在这里插入图片描述

  • 继续跟进:
    在这里插入图片描述
  • 在这个类中,可以发现帮我们加载了具体的解析器。
  • 查看HTTP的解析器:

在这里插入图片描述

  • 查看paras方法:

在这里插入图片描述

  • 进入方法中查看一下:

在这里插入图片描述

  • 继续查看:

在这里插入图片描述

  • 我们发现了springSecurityFilterChain,说明这个类真正的将FilterChainProxy注册了,并名字是springSecurityFilterChain,这也是上述配置的时候,名字为什么不能改的原因。

  • 继续查看parse方法,发现创建了一个FilterChain

在这里插入图片描述

  • 点进去查看方法:
  • 查看AuthenticationConfigBuilder部分:

在这里插入图片描述在这里插入图片描述

  • 我们可以发现在这个部分进行了很多的创建,这些都是我们在配置的时候指定的,这里是底层的具体实现,具体可以直接去相应的方法中查看。

Spring Security 源码版本为5.0.1.RELEASE

ATFWUS --Writing By 2020–05-09

原创文章 248 获赞 288 访问量 4万+

猜你喜欢

转载自blog.csdn.net/ATFWUS/article/details/106015038