是UBA项目中的spring security登录原理
会初始化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;
}
}
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.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的验证。
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中的页面,之前输入的是啥就是啥)。
应该是被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原理及教程