Spring security登录原理

是UBA项目中的spring security登录原理

web应用启动,初始化

会初始化spring security的拦截器(好像也是职责链模式),在web.xml中:

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

同时会实例化全部spring容器管理的bean,包括:

<bean id="filterSecurityInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
    <s:custom-filter before="FILTER_SECURITY_INTERCEPTOR" />
    <property name="accessDecisionManager" ref="accessDecisionManager" />
    <property name="objectDefinitionSource" ref="databaseDefinitionSource" />
</bean> 

<!-- DefinitionSource工厂,使用resourceDetailsService提供的URL-授权关系. -->
<bean id="databaseDefinitionSource" class="org.springside.modules.security.springsecurity.DefinitionSourceFactoryBean">
    <property name="resourceDetailsService" ref="resourceDetailsService" />
</bean>

<!-- 项目实现的URL-授权查询服务 -->
<bean id="resourceDetailsService" class="com.ebupt.UBA.service.security.ResourceDetailsServiceImpl" />

<!-- 授权判断配置, 将授权名称的默认前缀由ROLE_改为A_.-->
<bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
    <property name="decisionVoters">
        <list>
            <bean class="org.springframework.security.vote.RoleVoter">
                <property name="rolePrefix" value="A_" />
            </bean>
            <bean class="org.springframework.security.vote.AuthenticatedVoter" />
        </list>
    </property>
</bean> 

在resourceDetailsService类中会读取全部的resource-authority映射map信息(用于之后查询用户是否有权限):

/** * 从数据库查询URL--权限定义Map的实现类. * */
@Transactional(readOnly = true)
public class ResourceDetailsServiceImpl implements ResourceDetailsService {
    @Autowired
    private SecurityManager securityManager;

    /** * @see ResourceDetailsService#getRequestMap() */
    public LinkedHashMap<String, String> getRequestMap() throws Exception {
        List<Resource> resourceList = securityManager.getUrlResourceWithAuthorities();

        LinkedHashMap<String, String> requestMap = new LinkedHashMap<String, String>(resourceList.size());
        for (Resource resource : resourceList) {
            requestMap.put(resource.getValue(), resource.getAuthNames());
        }
        return requestMap;
    }
}

输入localhost:8080/UBA

web应用默认主页是index.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="org.springside.modules.security.springsecurity.SpringSecurityUtils" %>
<%@include file="/common/taglibs.jsp"%>
<% request.setAttribute("loginName",SpringSecurityUtils.getCurrentUserName()); %>

<c:if test="${loginName == 'roleAnonymous'}"><jsp:forward page="/common/login.jsp"/></c:if>
<c:if test="${loginName != 'roleAnonymous'}"><jsp:forward page="/homepage/home-page.action"/></c:if>

看到这个默认页面导入了spring security的包
利用SpringSecurityUtils.getCurrentUserName()获取spring 容器中的UserName(当然一开始是没有登录的)。
如果UserName有效,那么跳转home-page.action
如果UserName无效,那么跳转login.jsp

login,跳转到login.jsp

这个是登录页面:
Spring security登录原理_第1张图片
login.jsp主要是做很多校验工作,核心代码还是发送登录请求:

<form id="my_form" action="${ctx}/j_spring_security_check" method="post">
<input type="text" value="" id="loginName" name="j_username" style="width:185px;height:18px;border:0px;"/>
<input type="password" value="" id="password" name="j_password" style="width:185px;height:16px;border:0px;"/>
<input type="text" value="" id="captcha" name="j_captcha" style="width:89px;height:15px;margin-top:2px;border:0px;"/>
</form>

发送请求给j_spring_security_check
参数有j_username,j_password,j_captcha,这几个参数名字应该是不能变的,因为要调用spring security的验证。

spring根据输入的用户名获取用户信息

j_spring_security_check这个是spring security里面的约定。
applicationContext-security.xml中有

 <!-- 认证配置 -->
 <s:authentication-provider user-service-ref="userDetailsService">
  <!-- 可设置hash使用sha1或md5散列密码后再存入数据库 -->
  <s:password-encoder hash="md5" /> 
 </s:authentication-provider>
 <bean id="userDetailsService" class="com.ebupt.UBA.service.security.UserDetailsServiceImpl" /> 

之后会回调UserDetailsServiceImpl的loadUserByUsername方法,
其中:

@Transactional(readOnly = true)
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SecurityManager securityManager;

    /** * 获取用户Details信息的回调函数. */
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
     String username;
        if(userName.indexOf("re_")==0){
            username=userName.replace("re_", "");
       }else{
           username=userName;
       }
        User user = securityManager.findUserByLoginName(username);
        if (user == null)
            throw new UsernameNotFoundException("用户" + username + " 不存在");

        GrantedAuthority[] grantedAuths = obtainGrantedAuthorities(user);

        // mras_d中无以下属性,暂时全部设为true.
        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;
        String pwd=user.getPassword();
        if(userName.indexOf("re_")==0){         
                Md5PasswordEncoder md = new Md5PasswordEncoder();  
                System.out.println( md.encodePassword("1qaz2wsx",null));  
                pwd=md.encodePassword(pwd,null);
        }
        org.springframework.security.userdetails.User userdetail = new org.springframework.security.userdetails.User(
                user.getLoginName(), pwd, enabled, accountNonExpired, credentialsNonExpired,
                accountNonLocked, grantedAuths);

        return userdetail;
    }

    /** * 获得用户所有角色的权限集合. */
    private GrantedAuthority[] obtainGrantedAuthorities(User user) {
        Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
        for (Role role : user.getRoles()) {
            for (Authority authority : role.getAuthorities()) {
                authSet.add(new GrantedAuthorityImpl(authority.getName()));
            }
        }
        return authSet.toArray(new GrantedAuthority[authSet.size()]);
    }
}

这里根据用户的名字获取了用户密码等信息,new 了一个User类返回,中间存放了若干信息:用户名,密码,还有用户的全部权限名字。

new org.springframework.security.userdetails.User(
                user.getLoginName(), pwd, enabled, accountNonExpired, credentialsNonExpired,
                accountNonLocked, grantedAuths);

然后我理解的是spring security自己会比对用户的密码,如果正确,那么就会跳转至登录前的拦截页面(一开始的拦截页面应该是index.jsp,也就是home-page.action,其他的拦截页面就是在UBA_RESOURCE中的页面,之前输入的是啥就是啥)。

没有登录或者已经登录的情况下输入某个url

应该是被spring security的拦截器拦截了,我理解是DelegatingFilterProxy 。
拦截后根据容器中一开始查询出的资源权限映射关系,查询该url所需要的权限,和用户全部的权限进行比对。
如果用户满足了这些权限,跳转到该页面
如果用户没有满足这些权限,跳转到登录页面(这里做的有问题,因为UBA这个项目比较简单,一般是一个用户有超级权限,其他人都用这个用户来登。如果是多个用户有不同权限时,应该是跳转到”您没有权限“页面)。

spring security里的配置还是不太懂(还要琢磨琢磨):

<s:http auto-config="true" access-decision-manager-ref="accessDecisionManager">
    <s:intercept-url pattern="/common/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />  
    <s:form-login login-page="/common/login.jsp" default-target-url="/" authentication-failure-url="/common/login.jsp?error=true" />
    <s:logout logout-success-url="/common/login.jsp" />
    <s:remember-me />
    <!-- key="e37f4b31-0c45-11dd-bd0b-0800200c9a66"--> 
</s:http>

还有,用户在输入一个url的时候,如何判断这个url是一个资源?spring security默认url就是资源?还是要配置什么?
在spring security的拦截器拦截用户请求没有登录之后,就不会再走struts的拦截器了。
尝试在去掉web.xml中的springSecurityFilterChain后,用户可以不登录就进系统了。

在一个浏览器中登录后,这时用另外一个浏览器再进系统,需要登录吗?

试过是需要的,因为http request默认会带上session Id(如果有的话),所以spring security的实现其实是通过session Id 来获取出之前web 容器中的User实例的。
所以在web容器中可能有多个实例,如果是不同用户登录的话。

未完成

1、尝试在项目中修改数据库,配置用户的权限,尝试有不同用户不同权限登录场景。
2、自己手写一个分布式web应用,使用spring security实现登录效果。

参考

Spring Security原理及教程

你可能感兴趣的:(spring,Web应用)