您的位置:首页 > 编程语言 > Java开发

Spring Security3学习实例

2015-07-07 21:49 691 查看
Spring Security是什么?

Spring Security,这是一种基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。在Spring Framework基础上,Spring Security充分利用了依赖注入(DI,Dependency Injection)和面向切面技术。


下面开始通过一个简单实例来理解security是如何控制权限的

注:

实例中将同过Spring Security3框架实现成功登陆的人都能访问到main/common.
但只有拥有admin权限的用户才能访问main/admin.


比实列使用maven工具

新建一个maven-web工程(新建步骤maven搭建maven web工程



maven的pom.xml导入的包

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.sys</groupId>
<artifactId>SpringSecurity3</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>SpringSecurity3 Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.0.5.RELEASE</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.5.RELEASE</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.0.5.RELEASE</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>3.0.5.RELEASE</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
</dependencies>
<build>
<finalName>SpringSecurity3</finalName>
</build>
</project>


包结构图



首先提供一个controller来对外提供一个控制访问层

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/main")
public class MainController {
protected static Logger logger = Logger.getLogger("controller");

/**
* 跳转到commonpage页面
*
* @return
*/
@RequestMapping(value = "/common", method = RequestMethod.GET)
public String getCommonPage() {
logger.debug("Received request to show common page");
return "commonpage";
}

/**
* 跳转到adminpage页面
*
* @return
*/
@RequestMapping(value = "/admin", method = RequestMethod.GET)
public String getAadminPage() {
logger.debug("Received request to show admin page");
return "adminpage";

}


次controller中对外提供两个访问地址

main/common
main/admin


跟上我们的要求,拥有权限的人可以访问admin,其他普通用户只能访问common

接下来开启控制

web.xml中开启Spring3MVC和SpringSecurity3.

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> 
<!-- SpringSecurity必须的filter -->
<!-- 注意一点.最好是将DelegatingFilterProxy写在DispatcherServlet之前.否则SpringSecurity可能不会正常工作.  -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<!-- 过滤所有url都要安全处理 ,表示项目中所有路径的资源都要经过SpringSecurity.  -->
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 指定的SpringSecurity配置 :spring-security.xml  -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-security.xml
/WEB-INF/applicationContext.xml
</param-value>
</context-param>

<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/spring-servle.xml</param-value>
      </init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

</web-app>


配置spring的文件

spring-servle.xml:配置声明一个视图解析器.在控制器中会根据JSP名映射到/ WEB-INF/jsp中相应的位置.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 
<!-- 定义一个视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />

</beans>


applicationContext.xml:spring的注入机制等

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> 
<!-- 激活spring的注解. -->
<context:annotation-config />

<!-- 扫描注解组件并且自动的注入spring beans中.
例如,他会扫描@Controller 和@Service下的文件.所以确保此base-package设置正确. -->
<context:component-scan base-package="com.*"/>

<!-- 配置注解驱动的Spring MVC Controller 的编程模型.注:次标签只在 Servlet MVC工作! -->
<mvc:annotation-driven />

</beans>


接着是创建JSP页面

commonpage.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Common Page</h1>
<p>每个人都能访问的页面.</p>
<a href="/SpringSecurity3/main/admin"> Go AdminPage </a>
<br />
<a href="/SpringSecurity3/auth/login">退出登录</a>

</body>
</html>


adminpage.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Admin Page</h1>
<p>管理员页面</p>
<a href="/SpringSecurity3/auth/login">退出登录</a>
</body>
</html>


loginpage.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Login</h1>

<div id="login-error">${error}</div>

<form action="../j_spring_security_check" method="post">

<p>
<label for="j_username">Username</label> <input id="j_username"
name="j_username" type="text" />
</p>

<p>
<label for="j_password">Password</label> <input id="j_password"
name="j_password" type="password" />
</p>

<input type="submit" value="Login" />

</form>

</body>
</html>


deniedpage.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>你的权限不够!</h1>
<p>只有拥有Admin权限才能访问!</p>
<a href="/spring3-security-integration/auth/login">退出登录</a>
</body>
</html>


还有一个controller用于映射上面两个JSP页面..

package org.liukai.tutorial.controller;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("auth")
public class LoginLogoutController {

protected static Logger logger = Logger.getLogger("controller");

/**
* 指向登录页面
*/
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String getLoginPage(
@RequestParam(value = "error", required = false) boolean error,
ModelMap model) {

logger.debug("Received request to show login page");

if (error == true) {
// Assign an error message
model.put("error",
"You have entered an invalid username or password!");
} else {
model.put("error", "");
}
return "loginpage";

}

/**
* 指定无访问额权限页面
*
* @return
*/
@RequestMapping(value = "/denied", method = RequestMethod.GET)
public String getDeniedPage() {

logger.debug("Received request to show denied page");

return "deniedpage";

}
}


该controller实现了两个映射

auth/login     --显示Login页面
auth/denied    --显示拒绝访问页面


spring-security.xml的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd"> 
<!--  Spring-Security 的配置 -->
<!-- 注意开启use-expressions.表示开启表达式.     -->
<security:http auto-config="true" use-expressions="true" access-denied-page="/auth/denied" >

<security:intercept-url pattern="/auth/login" access="permitAll"/>
<security:intercept-url pattern="/main/admin" access="hasRole('ROLE_ADMIN')"/>
<security:intercept-url pattern="/main/common" access="hasRole('ROLE_USER')"/>

<security:form-login
login-page="/auth/login"
authentication-failure-url="/auth/login?error=true"
default-target-url="/main/common"/>

<security:logout
invalidate-session="true"
logout-success-url="/auth/login"
logout-url="/auth/logout"/>

</security:http>

<!-- 指定一个自定义的authentication-manager :customUserDetailsService -->
<security:authentication-manager>
<security:authentication-provider user-service-ref="customUserDetailsService">
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>

<!-- 对密码进行MD5编码 -->
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

<!--
通过 customUserDetailsService,Spring会自动的用户的访问级别.
也可以理解成:以后我们和数据库操作就是通过customUserDetailsService来进行关联.
-->
<bean id="customUserDetailsService" class="org.liukai.tutorial.service.CustomUserDetailsService"/>

</beans>


在配置中可以看到三个URL对应的三个权限

<security:intercept-url pattern="/auth/login" access="permitAll"/>
<security:intercept-url pattern="/main/admin" access="hasRole('ROLE_ADMIN')"/>
<security:intercept-url pattern="/main/common" access="hasRole('ROLE_USER')"/>


需要注意的是这里可以使用了SpringEL表达式来指定角色的访问.

以下是表达式对应的用法

表达式     说明
hasRole([role])     返回 true 如果当前主体拥有特定角色。
hasAnyRole([role1,role2])     返回 true 如果当前主体拥有任何一个提供的角色 (使用逗号分隔的字符串队列)
principal     允许直接访问主体对象,表示当前用户
authentication     允许直接访问当前 Authentication对象 从SecurityContext中获得
permitAll     一直返回true
denyAll     一直返回false
isAnonymous()     如果用户是一个匿名登录的用户 就会返回 true
isRememberMe()     如果用户是通过remember-me 登录的用户 就会返回 true
isAuthenticated()     如果用户不是匿名用户就会返回true
isFullyAuthenticated()     如果用户不是通过匿名也不是通过remember-me登录的用户时, 就会返回true。


所以

<security:intercept-url pattern="/auth/login" access="permitAll"/>


表示所有的人都可以访问/auth/login.

<security:intercept-url pattern="/main/admin" access="hasRole('ROLE_ADMIN')"/>
<security:intercept-url pattern="/main/common" access="hasRole('ROLE_USER')"/>


则表示只有拥有对应的角色才能访问.

<security:form-login  login-page="/auth/login"
authentication-failure-url="/auth/login?error=true"
default-target-url="/main/common"/>


表示通过 /auth/login这个映射进行登录.
如果验证失败则返回一个URL:/auth/login?error=true
如果登录成功则默认指向:/main/common

<security:logout
invalidate-session="true"
logout-success-url="/auth/login"
logout-url="/auth/logout"/>


很简单.我们开启了session失效功能.
注销URL为:/auth/logout
注销成功后转向:/auth/login

<!-- 指定一个自定义的authentication-manager :customUserDetailsService -->
<security:authentication-manager>
<security:authentication-provider user-service-ref="customUserDetailsService">
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>

<!-- 对密码进行MD5编码 -->
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

<!--
通过 customUserDetailsService,Spring会自动的用户的访问级别.
也可以理解成:以后我们和数据库操作就是通过customUserDetailsService来进行关联.
-->
<bean id="customUserDetailsService" class="org.liukai.tutorial.service.CustomUserDetailsService"/>


一个自定义的CustomUserDetailsService,是实现SpringSecurity的UserDetailsService接口,但我们重写了他即便于我们进行数据库操作.

DbUser.java

package org.liukai.tutorial.domain;

public class DbUser {

private String username;
private String password;
private Integer access;

//getter/setter

}


通过一个初始化的List来模拟数据库操作.

UserDao.java

package org.liukai.tutorial.dao;

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

import org.apache.log4j.Logger;
import org.liukai.tutorial.domain.DbUser;

public class UserDao {

protected static Logger logger = Logger.getLogger("dao");

public DbUser getDatabase(String username) {

List<DbUser> users = internalDatabase();

for (DbUser dbUser : users) {
if (dbUser.getUsername().equals(username) == true) {
logger.debug("User found");
return dbUser;
}
}
logger.error("User does not exist!");
throw new RuntimeException("User does not exist!");

}

/**
* 初始化数据
*/
private List<DbUser> internalDatabase() {

List<DbUser> users = new ArrayList<DbUser>();
DbUser user = null;

user = new DbUser();
user.setUsername("admin");

// "admin"经过MD5加密后
user.setPassword("21232f297a57a5a743894a0e4a801fc3");
user.setAccess(1);

users.add(user);

user = new DbUser();
user.setUsername("user");

// "user"经过MD5加密后
user.setPassword("ee11cbb19052e40b07aac0ca060c23ee");
user.setAccess(2);

users.add(user);

return users;

}
}


自定义UserDetailsService .可以通过继承UserDetailsService
来达到灵活的自定义UserDetailsService

CustomUserDetailsService.java

package org.liukai.tutorial.service;

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

import org.apache.log4j.Logger;
import org.liukai.tutorial.dao.UserDao;
import org.liukai.tutorial.domain.DbUser;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

/**
* 一个自定义的service用来和数据库进行操作. 即以后我们要通过数据库保存权限.则需要我们继承UserDetailsService
*
* @author liukai
*
*/
public class CustomUserDetailsService implements UserDetailsService {

protected static Logger logger = Logger.getLogger("service");

private UserDao userDAO = new UserDao();

public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {

UserDetails user = null;

try {

// 搜索数据库以匹配用户登录名.
// 我们可以通过dao使用JDBC来访问数据库
DbUser dbUser = userDAO.getDatabase(username);

// Populate the Spring User object with details from the dbUser
// Here we just pass the username, password, and access level
// getAuthorities() will translate the access level to the correct
// role type

user = new User(dbUser.getUsername(), dbUser.getPassword()
.toLowerCase(), true, true, true, true,
getAuthorities(dbUser.getAccess()));

} catch (Exception e) {
logger.error("Error in retrieving user");
throw new UsernameNotFoundException("Error in retrieving user");
}

return user;
}

/**
* 获得访问角色权限
*
* @param access
* @return
*/
public Collection<GrantedAuthority> getAuthorities(Integer access) {

List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2);

// 所有的用户默认拥有ROLE_USER权限
logger.debug("Grant ROLE_USER to this user");
authList.add(new GrantedAuthorityImpl("ROLE_USER"));

// 如果参数access为1.则拥有ROLE_ADMIN权限
if (access.compareTo(1) == 0) {
logger.debug("Grant ROLE_ADMIN to this user");
authList.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
}

return authList;
}
}


http://localhost:8080/SpringSecurity3/auth/login

本文参考来自这里
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: