Spring security登录原理

来源:互联网 发布:mac右上角图标管理 编辑:程序博客网 时间:2024/04/29 20:18

是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

这个是登录页面:
这里写图片描述
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原理及教程

0 0
原创粉丝点击