`
ll_feng
  • 浏览: 382007 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

springsecurity学习笔之二:实现一个基于数据库的简单权限系统

    博客分类:
  • j2ee
阅读更多
这里在一个web工程中,通过三张表,实现用户、角色、权限的关系实现一个相对简单的权限系统。没有考虑对资源(URL)的控制
一、在web工程中加入springsecurity的支持,主要jar包

二、配置web容器:web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">


	<!--
		- Location of the XML file that defines the root application context -
		Applied by ContextLoaderListener.
	-->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath:applicationContext.xml
            classpath:applicationContext-sec.xml
            <!-- classpath:applicationContext-common-business.xml
            classpath:applicationContext-common-authorization.xml
            classpath:applicationContext-security.xml -->
		</param-value>
	</context-param>


	<filter>
		<filter-name>localizationFilter</filter-name>
		<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
	</filter>

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


	<filter-mapping>
		<filter-name>localizationFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>
			org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<!--
		- Loads the root application context of this web app at startup. - The
		application context is then available via -
		WebApplicationContextUtils.getWebApplicationContext(servletContext).
	-->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<error-page></error-page>

	<session-config>
		<session-timeout>20</session-timeout>
	</session-config>

	<!-- 出错页面定义 -->
	<error-page>
		<exception-type>java.lang.Throwable</exception-type>
		<location>/commons/error.jsp</location>
	</error-page>
	<error-page>
		<error-code>500</error-code>
		<location>/commons/500.jsp</location>
	</error-page>
	<error-page>
		<error-code>404</error-code>
		<location>/commons/404.jsp</location>
	</error-page>
	<error-page>
		<error-code>403</error-code>
		<location>/commons/403.jsp</location>
	</error-page>

	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
</web-app>



三、配置spring : applicationContext-springsecurity.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
	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-3.0.xsd">
	<http auto-config="true">
		<form-login login-page="/login.jsp"/>
		<intercept-url pattern="/login.jsp*" filters="none"/>
		<intercept-url pattern="/*" access="ROLE_USER" /><!-- 配置资源的权限的关系 -->
		<!-- <http-basic /> -->
	</http>
	<authentication-manager alias="authenticationManager">
		<authentication-provider user-service-ref="userDetailService">
			<!-- 
			<user-service>
				<user authorities="ROLE_USER" name="guest" password="guest" />
			</user-service>
			 -->
			 <password-encoder hash="plaintext"></password-encoder>
			 
		</authentication-provider>
	</authentication-manager>
	
	<beans:bean id="userDetailService" class="com.harmony.cap.auth.service.UserDetailsServiceImpl"/>
</beans:beans>


四、增加自己定义的登录页面:login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>My JSP 'index.jsp' starting page</title>
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">    
	<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
	<meta http-equiv="description" content="This is my page">
	<!--
	<link rel="stylesheet" type="text/css" href="styles.css">
	-->
  </head>
  
  <body>
    This is my JSP page. <br>
    <form name='f' action='${pageContext.request.contextPath }/j_spring_security_check'  method='POST'>

  User:<input type='text' name='j_username' value=''>

  Password:<input type='password' name='j_password'/>

  <input name="submit" type="submit"/>

  <input name="reset" type="reset"/>

</form>
  </body>
</html>



五、增加权限类:User.java、Role.java、Privileage.java
1、User.java
package cn.ibeans.ssh.model;

import java.sql.Blob;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

import cn.ibeans.common.utils.reflection.ConvertUtils;

/**
 * 用户.
 * 
 * 使用JPA annotation定义ORM关系.
 * 使用Hibernate annotation定义JPA 1.0未覆盖的部分.
 * 
 * @author calvin
 */
@Entity
//表名与类名不相同时重新定义表名.
@Table(name = "SYS_USERS")
//默认的缓存策略.
//@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User extends IdEntity {

	private String loginId;
	private String password;//为简化演示使用明文保存的密码
	private String name;
	private String email;
	private String resume;
	private Blob photo;
	private List<Role> roleList = new ArrayList();//有序的关联对象集合

	//字段非空且唯一, 用于提醒Entity使用者及生成DDL.
	@Column(nullable = false, unique = true)
	public String getLoginId() {
		return loginId;
	}
	
	public void setLoginId(String loginId) {
		this.loginId = loginId;
	}

	@Column(name="password")
	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
	
	@Column(name="resume")
	public String getResume() {
		return resume;
	}

	public void setResume(String resume) {
		this.resume = resume;
	}

	@Column(name="photo")
	public Blob getPhoto() {
		return photo;
	}

	public void setPhoto(Blob photo) {
		this.photo = photo;
	}

	@Column(name="name")
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Column(name="email")
	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	//多对多定义
	@ManyToMany
	//中间表定义,表名采用默认命名规则
	@JoinTable(name = "SYS_USER_ROLE", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "ROLE_ID") })
	//Fecth策略定义
	@Fetch(FetchMode.SUBSELECT)
	//集合按id排序.
	@OrderBy("id")
	//集合中对象id的缓存.
	//@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
	public List<Role> getRoleList() {
		return roleList;
	}

	public void setRoleList(List<Role> roleList) {
		this.roleList = roleList;
	}

	/**
	 * 用户拥有的角色名称字符串, 多个角色名称用','分隔.
	 */
	//非持久化属性.
	@Transient
	public String getRoleNames() {
		return "";//ConvertUtils.convertElementPropertyToString(roleList, "name", ", ");
	}

	/**
	 * 用户拥有的角色id字符串, 多个角色id用','分隔.
	 */
	//非持久化属性.
	@Transient
	@SuppressWarnings("unchecked")
	public List<Long> getRoleIds() {
		return null;//ConvertUtils.convertElementPropertyToList(roleList, "id");
	}

	@Override
	public String toString() {
		return ToStringBuilder.reflectionToString(this);
	}
}


2、Role.java
package cn.ibeans.ssh.model;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;


/**
 * 角色.
 * 
 * 注释见{@link User}.
 * 
 * @author calvin
 */
@Entity
@Table(name = "SYS_ROLES")
//@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Role extends IdEntity {

	private String name;
	private List<Privilege> privilegeList = new ArrayList();//Lists.newArrayList();

	public Role() {

	}

	public Role(Long id, String name) {
		this.id = id;
		this.name = name;
	}

	@Column(nullable = false, unique = true)
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@ManyToMany
	@JoinTable(name = "SYS_ROLE_PRIVILEGE", joinColumns = { @JoinColumn(name = "ROLE_ID") }, inverseJoinColumns = { @JoinColumn(name = "PRIVILEGE_ID") })
	@Fetch(FetchMode.SUBSELECT)
	@OrderBy("id")
	//@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
	public List<Privilege> getPrivilegeList() {
		return privilegeList;
	}

	public void setPrivilegeList(List<Privilege> privilegeList) {
		this.privilegeList = privilegeList;
	}

	@Transient
	public String getAuthNames() {
		return "";//ConvertUtils.convertElementPropertyToString(authorityList, "name", ", ");
	}

	@Transient
	@SuppressWarnings("unchecked")
	public List<Long> getAuthIds() {
		return null;//ConvertUtils.convertElementPropertyToList(authorityList, "id");
	}

	@Override
	public String toString() {
		return ToStringBuilder.reflectionToString(this);
	}
}



3、Privilege.java
package cn.ibeans.ssh.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;


/**
 * 权限.
 * 
 * 注释见{@link User}.
 * 
 * @author calvin
 */
@Entity
@Table(name = "SYS_PRIVILEGE")
//@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Privilege extends IdEntity {

	/**
	 * SpringSecurity中默认的角色/授权名前缀.
	 */
	public static final String AUTHORITY_PREFIX = "ROLE_";

	private String name;

	public Privilege() {
	}

	public Privilege(Long id, String name) {
		this.id = id;
		this.name = name;
	}

	@Column(nullable = false, unique = true)
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Transient
	public String getPrefixedName() {
		return AUTHORITY_PREFIX + name;
	}

	@Override
	public String toString() {
		return ToStringBuilder.reflectionToString(this);
	}
}


以上三个类,实际上还需要两个中间表。为了方便我采用系统自动建表。即:
<prop key="hibernate.hbm2ddl.auto">update</prop>


六、实现自定义的用户管理类:UserDetailsServiceImpl.java
package cn.ibeans.ssh.auth.service;

import java.util.HashSet;
import java.util.Set;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Transactional;

import cn.ibeans.ssh.auth.model.Privilege;
import cn.ibeans.ssh.auth.model.Role;
import cn.ibeans.ssh.auth.model.User;

/**
 * 实现SpringSecurity的UserDetailsService接口,实现获取用户Detail信息的回调函数.
 * 
 * @author calvin
 */
@Transactional(readOnly = true)
public class UserDetailsServiceImpl implements UserDetailsService {

	Logger logger = Logger.getLogger(this.getClass());
	private AccountManager accountManager;

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

		Set<GrantedAuthority> grantedAuths = obtainGrantedAuthorities(user);

		//-- mini-web示例中无以下属性, 暂时全部设为true. --//
		boolean enabled = true;
		boolean accountNonExpired = true;
		boolean credentialsNonExpired = true;
		boolean accountNonLocked = true;

		UserDetails userdetails = new org.springframework.security.core.userdetails.User(user.getLoginId(), user
				.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuths);
		logger.debug("用户详细信息已获得。"+userdetails.getUsername()+"/"+userdetails.getPassword());
		return userdetails;
	}

	/**
	 * 获得用户所有角色的权限集合.
	 */
	private Set<GrantedAuthority> obtainGrantedAuthorities(User user) {
		Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
		for (Role role : user.getRoleList()) {
			for (Privilege privilege : role.getPrivilegeList()) {
				authSet.add(new GrantedAuthorityImpl(privilege.getPrefixedName()));
			}
		}
		return authSet;
	}

	@Autowired
	public void setAccountManager(AccountManager accountManager) {
		this.accountManager = accountManager;
	}
}



七、初化数据
为了能让系统能快速运行起来,可直接在数据库中插入基本数据:
分别user、role、privilege表及其中间表中插入测试数据,建立起它们之间的关系。系统基本可以运行了。


八、小结
1、web.xml里的配置要把springsecurity的拦截配置在前面,否则会被struts2首先拦截并报找不到action的错误。
2、自定义登录页面中的url和用户名、密码属性的定义不能随意。springsecurity自带的过虑器(UsernamePasswordAuthenticationFilter)中有定义:
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "j_username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "j_password";
    public static final String SPRING_SECURITY_LAST_USERNAME_KEY = "SPRING_SECURITY_LAST_USERNAME";

    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
    private boolean postOnly = true;

    //~ Constructors ===================================================================================================

    public UsernamePasswordAuthenticationFilter() {
        super("/j_spring_security_check");
    }


3、权限表(privilege)的数据必须有与springsecurity配置中的权限一致的值,用户才能获得授权,否则无法通过。
比如:
<http auto-config="true">
		<form-login login-page="/login.jsp"/>
		<intercept-url pattern="/login.jsp*" filters="none"/>
		<intercept-url pattern="/*" access="ROLE_USER" /><!-- 配置资源的权限的关系 -->
		<!-- <http-basic /> -->
	</http>

那么,privilege表中,要有一项为“USER“的权限名。才能访问”/*“下的资源。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics