读Spring实战(第四版)概括—保护Web应用(Spring Security)

本文深入探讨了Spring Security,一个为Spring应用程序提供安全性的框架。文章涵盖了Spring Security的实现、模块、用户认证策略,以及XML和Java配置的示例。此外,文章还详细介绍了SecurityContextHolder、Authentication、Principal、GrantedAuthority等重要组件,以及Spring Security的认证流程和基于数据库的用户认证Demo。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

Spring Security是为基于Spring的应用程序提供声明式安全保护的安全性框架。Spring Security提供了完整的安全性解决方案,它能够在Web请求级别和方法调用级别处理身份认证和授权。因为基于Spring框架,所以Spring Security充分利用了依赖注入(dependency injection,DI)和面向切面的技术。

二、Spring Security详情

2.1.实现

Spring Security从两个角度来解决安全性问题:使用Servlet规范中的Filter保护Web请求并限制URL级别的访问;使用Spring AOP保护方法调用——借助于对象代理和使用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法。

对于Spring Security来说主要提供的两个安全服务是指:认证(Authentication)和 授权(Authorization);认证是用来确定当前用户,授权是判断一个用户是否有访问某个安全对象的权限。

2.2.Spring Security模块

下面常用的模块包括:配置(config)、核心(core)和Web是必须要的;标签库也是常用的模块。

Spring Security标签库支持的标签包括:

使用方式如下所示:

<http auto-config="true" use-expressions="true">
    <intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')">
</http>

2.3.Spring Security用户认证

Spring Security中提供了多种的用户认证策略,常用的认证策略包括(认证将在后面详细说明过程):

         1.基于内存的用户存储(下面第一个Demo就是基于内存的用户存储)

         2.基于数据库表进行认证(将在后面展示Demo)

         3.基于LDAP进行用户认证(将在后面展示Demo)

三、Spring Security Demo

3.1.基于传统的XML实现

配置SpringSecurity(web.xml), DelegatingFilterProxy是Servlet Filter的代理类,通过这个类可以让Servlet Filter通过Spring来管理。它可以链接任意多个其他的过滤器,根据这些过滤器提供不同的安全特性.

<!-- SpringSecuity配置 -->
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

SpringSecurity具体配置(spring-security.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- 静态资源不需要安全验证 -->
	<http pattern="/resources/**" security="none"/>

	<http auto-config="true" use-expressions="false">
		<intercept-url pattern="/admin**" access="ROLE_ADMIN" />
		<intercept-url pattern="/user**" access="ROLE_USER" />
		<!-- HTTP基本认证 
		<http-basic/> -->
		<!-- 通过form-login设置自定义的登陆界面
		<form-login/> -->
		<!-- 通过logout设置退出登陆地址等
		<logout/> -->
	</http>

	<authentication-manager>
	  	<authentication-provider>
	    	<user-service>
				<user name="admin" password="admin" authorities="ROLE_ADMIN" />
				<user name="wuxinhui" password="123456" authorities="ROLE_USER" />
	    	</user-service>
	  	</authentication-provider>
	</authentication-manager>
</beans:beans>

在最小化配置中只需要配置<http>节点(对特定的http请求基于安全考虑进行配置)和<authentication-manager>认证管理器。其中http-basic认证是弹出Dialog框进行用户名密码输入而不是完整的页面。

最后就是需要准备相应的controller,如下所示:

@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@RequestMapping(value = { "/", "/welcome" }, method = RequestMethod.GET)
	public ModelAndView welcomePage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security");
		model.addObject("message", "This is welcome page!");
		model.setViewName("hello");
		return model;
	}

	@SuppressWarnings("unused")
	@RequestMapping(value = "/admin", method = RequestMethod.GET)
	public ModelAndView adminPage() {
		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security");
		model.addObject("message", "This is admin page!");
		model.setViewName("admin");
		return model;
	}
	
	@RequestMapping(value = "/user", method = RequestMethod.GET)
	public ModelAndView userPage() {
		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security");
		model.addObject("message", "This is user page!");
		model.setViewName("admin");
		return model;
	}
	
}

项目结构如下所示:

3.2.基于Java配置实现

主要是实现 WebSecurityConfigurerAdapter适配器的类,如下所示。当使用@EnableWebSecurity注解时,其实就可以使用Spring Security,这时Spring Security会生成一串密码(用户名默认是user),例如:Using generated security password: c6ef06a4-6b1a-4932-b834-87d3923c919b。和XML配置类似,Java配置也需要两个基本的配置:httpSecurity和AuthenticationManager。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
				.antMatchers("/", "/home").permitAll()
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login")
				.permitAll()
				.and()
			.logout()
				.permitAll();
	}
	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
		authenticationManagerBuilder
			.inMemoryAuthentication()
				.passwordEncoder(new BCryptPasswordEncoder())
				.withUser("admin")
				.password(new BCryptPasswordEncoder().encode("admin"))
				.roles("USER", "ADMIN");
	}
}

假如:你拥有ROLE_USER角色,但是去访问ROLE_ADMIN的资源,那你的连接将会被拒绝,如下图所示:

四、Spring Security中的重要组件

4.1.SecurityContextHolder/SecurityContext

SecurityContextHolder会将SecurityContext与当前执行的上下文相关连,并定义了SecurityContext的相关操作,如初始化,清空,读取等。SecurityContextHolder并没有直接实现这些操作,而是使用了策略模式,由一个SecurityContextHolderStrategy接口,来完成真正的逻辑。

Spring Security提供了3个实现:

         GlobalSecurityContextHolderStrategy,在这种策略下,JVM所有的对象共享一个SecurityContext对象。一般在胖客户端使用,一个客户端就是一个用户代理。

         ThreadLocalSecurityContextHolderStrategy,这种策略下,每个线程关联一个SecurityContext。每次都从当前线程读取或存放SecurityContext。一般用于为多个用户服务的服务器,每个用户一个线程。是最常用的模式,也是Spring Security缺省的策略。用户注销时,应该把该用户所在线程的SecurityContext清除掉。

         InheritableThreadLocalSecurityContextHolderStrategy,这种策略是InheritableThreadLocal版本的实现。

      有两种方法可以设置策略:1)用要使用策略的类名作为系统属性spring.security.strategy的值。2)直接调用SecurityContextHolder的静态方法setStrategyName。(为什么要做成静态的,而不是普通的依赖注入的方式)

4.2. Authentication 、UserDetails、Principal、GrantedAuthority 

Spring Security中使用Authentication存储当前用户的主要信息。Authentication源码结构如下所示:

public interface Authentication extends Principal, Serializable {
	// 获取当前用户被授权的角色,比如ROLE_USER,ROLE_ADMIN
	Collection<? extends GrantedAuthority> getAuthorities();
	// 与web身份验证相关的http请求信息,
	Object getCredentials();
	// 存储一些额外的信息,其实就是SessionId和客户端请求的Ip
	Object getDetails();
	// 存放用户的身份信息,比如用户名、密码等(可以强制转换为UserDetails)
	Object getPrincipal();
	// 是否通过认证
	boolean isAuthenticated();
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

我们可以通过打印出一些信息来更好的说明:

System.out.println("sessionID:" + ((WebAuthenticationDetails)SecurityContextHolder.getContext().getAuthentication().getDetails()).getSessionId() + 
		" User:" + ((UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername() + 
		" current thread:" + Thread.currentThread().getId() + "-" + Thread.currentThread().getName());	
System.out.println("authority list:");
for (GrantedAuthority authorityGranter : SecurityContextHolder.getContext().getAuthentication().getAuthorities()) {
	System.out.println(authorityGranter.getAuthority());
}

4.3. AuthenticationManager、ProviderManager和AuthenticationProvider

AuthenticationManager是用来处理认证请求的接口,接口如下所示。该接口只有一个authenticate方法用来接受认证请求的参数,当认证通过时,就会返回一个封装了当前用户信息的Authentication对象。

public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
}

而ProviderManager则是AuthenticationManager的实现类,ProviderManager也并不直接进行验证,而是通过AuthenticationProvider列表来进行认证,如果有一个认证通过,就会跳出循环,认证成功。

当我们使用 authentication-provider 元素来定义一个 AuthenticationProvider 时,如果没有指定对应关联的 AuthenticationProvider 对象,Spring Security 默认会使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails,其中包括用户名、密码和所拥有的权限等。所以如果我们需要改变认证的方式,我们可以实现自己的 AuthenticationProvider;如果需要改变认证的用户信息来源,我们可以实现 UserDetailsService。

实现了自己的 AuthenticationProvider 之后,我们可以在配置文件中这样配置来使用我们自己的 AuthenticationProvider。其中 myAuthenticationProvider 就是我们自己的 AuthenticationProvider 实现类对应的 bean。

<security:authentication-manager>
	<security:authentication-provider ref="myAuthenticationProvider"/>
</security:authentication-manager>

实现了自己的 UserDetailsService 之后,我们可以在配置文件中这样配置来使用我们自己的 UserDetailsService。其中的 myUserDetailsService 就是我们自己的 UserDetailsService 实现类对应的 bean。

<security:authentication-manager>
	<security:authentication-provider user-service-ref="myUserDetailsService"/>
</security:authentication-manager>

五、Spring Security认证

Spring Security进行用户认证的步骤如下所示:

        1.用户使用用户名和密码进行登录。

   2.Spring Security将获取到的用户名和密码封装成一个Authentication接口的实现类,比如常用的UsernamePasswordAuthenticationToken(用户名密码认证)、AnonymousAuthenticationToken(匿名认证)、RememberMeAuthenticationToken(记住身份认证)。

         3.将上述产生的Authentication对象传递给AuthenticationManager的实现类ProviderManager进行认证。

        4.ProviderManager依次调用各个AuthenticationProvider进行认证,例如使用DaoAuthenticationProvider。但是认证方法是存放在DaoAuthenticationProvider的父类AbstractUserDetailsAuthenticationProvider中(其实就是实现的authenticate方法),分析源码可以看到该方法会调用retrieveUser方法获取一个userDetails对象(基于此可以实现自定义服务获取该对象,比如从数据库中获取),然后再会通过调用additionalAuthticationChecks放来进行用户的认证。而additionalAuthticationChecks中则会调用PasswordEncoder接口中的isPassordValid方法进行验证,默认是PlaintextPasswordEncoder类实现,代码如下所示(其实就是文本对比,没有任何的加密等等):

public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
    String pass1 = encPass + "";

	// Strict delimiters is false because pass2 never persisted anywhere
	// and we want to avoid unnecessary exceptions as a result (the
	// authentication will fail as the encodePassword never allows them)
	String pass2 = mergePasswordAndSalt(rawPass, salt, false);

	if (ignorePasswordCase) {
		// Note: per String javadoc to get correct results for Locale insensitive, use
		// English
		pass1 = pass1.toLowerCase(Locale.ENGLISH);
		pass2 = pass2.toLowerCase(Locale.ENGLISH);
	}
	return PasswordEncoderUtils.equals(pass1, pass2);
}

认证成功后返回一个封装了用户权限等信息的Authentication对象。

         5.将AuthenticationManager返回的Authentication对象赋予给当前的SecurityContext。

六、Spring Security用户认证Demo

基于内存的用户存储认证上面Demo中已有,可以参考上面的。

6.1.基于数据库表进行认证

这里我将展示完整的基于数据库表进行认证的Demo。要实现数据库表认证,我们需要实现UserDetailsSerivce接口,该接口只有一个方法loanUserByUsername。通过重写该方法,我们可以自己组装返回UserDetails来进行验证判断(配置自定义用户服务实现)。

1.首先我们需要创建数据库权限相关的表,下面是我建表SQL。主要有三个对象:用户(user)、角色(role)、权限(resource)。

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for resources
-- ----------------------------
DROP TABLE IF EXISTS `resource`;
CREATE TABLE `resource` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `resource_name` varchar(100) NOT NULL,
  `resource_type` varchar(100) NOT NULL,
  `resource_content` varchar(200) NOT NULL,
  `resource_desc` varchar(200) NOT NULL,
  `enabled` int(2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-- ----------------------------
-- Records of resources
-- ----------------------------
INSERT INTO `resource` VALUES ('1', '管理员资源', 'requesturl', '/login', '进入管理员页面', '1');
INSERT INTO `resource` VALUES ('2', '普通用户', 'requesturl', '/index', '登录首页', '1');
 
-- ----------------------------
-- Table structure for resource_authority
-- ----------------------------
DROP TABLE IF EXISTS `role_resource`;
CREATE TABLE `role_resource` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rid` int(11) DEFAULT NULL,
  `aid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-- ----------------------------
-- Records of resource_authority
-- ----------------------------
INSERT INTO `role_resource` VALUES ('1', '1', '1');
INSERT INTO `role_resource` VALUES ('2', '1', '1');
INSERT INTO `role_resource` VALUES ('3', '2', '2');
 
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'ROLE_ADMIN');
INSERT INTO `role` VALUES ('2', 'ROLE_USER');
 
-- ----------------------------
-- Table structure for role_user
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-- ----------------------------
-- Records of role_user
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', '1', '1');
INSERT INTO `user_role` VALUES ('2', '2', '2');
 
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `password` varchar(60) DEFAULT NULL,
  `enabled` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'admin', '21232f297a57a5a743894a0e4a801fc3', '1');
INSERT INTO `user` VALUES ('2', 'user', '21232f297a57a5a743894a0e4a801fc3', '1');

2.实体

有数据库,肯定就需要有实体,实体如下所示:

/**
 * 资源类
 * 
 * @author Administrator
 *
 */
public class Resource {
	private int id;
	private String resourceName; //资源名
	private String resourceType; // 资源类型:在Spring Security中分为对请求的保护和对方法的保护
	private String resourceContent; // 资源内容
	private String resourceDesc; // 资源描述
	private int enabled; // 是否启用
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getResourceName() {
		return resourceName;
	}
	public void setResourceName(String resourceName) {
		this.resourceName = resourceName;
	}
	public String getResourceType() {
		return resourceType;
	}
	public void setResourceType(String resourceType) {
		this.resourceType = resourceType;
	}
	public String getResourceContent() {
		return resourceContent;
	}
	public void setResourceContent(String resourceContent) {
		this.resourceContent = resourceContent;
	}
	public String getResourceDesc() {
		return resourceDesc;
	}
	public void setResourceDesc(String resourceDesc) {
		this.resourceDesc = resourceDesc;
	}
	public int getEnabled() {
		return enabled;
	}
	public void setEnabled(int enabled) {
		this.enabled = enabled;
	}
}
/**
 * 角色类
 * <p>一个角色可以有多种权限
 * 
 * @author Administrator
 *
 */
public class Role {
	private int id;
	private String rolename; // 角色
	private List<Resource> resources;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getRolename() {
		return rolename;
	}
	public void setRolename(String rolename) {
		this.rolename = rolename;
	}
	public List<Resource> getResources() {
		return resources;
	}
	public void setResources(List<Resource> resources) {
		this.resources = resources;
	}
}
/**
 * 用户类
 * <p>一个用户可以拥有多个角色
 * 
 * @author Administrator
 *
 */
public class User {
	private int id;
	private String username; // 用户名
	private String password; // 密码
	private int enabled; // 是否启用
	private List<Role> roles; //角色
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public int getEnabled() {
		return enabled;
	}
	public void setEnabled(int enabled) {
		this.enabled = enabled;
	}
	public List<Role> getRoles() {
		return roles;
	}
	public void setRoles(List<Role> roles) {
		this.roles = roles;
	}
}

3.dao获取用户信息

@Repository
public class UserDao {
	
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	public User queryUserByName(String userName) {
		Map<String, Object> result = jdbcTemplate.queryForMap("select * from t_user where t_user.username=?", userName);
		User user = new User();
		user.setId((int)result.get("id"));
		user.setUsername((String)result.get("username"));
		user.setPassword((String)result.get("password"));
		user.setEnabled((int)result.get("enabled"));
		
		// 获取user权限信息
		List<Map<String, Object>> listResult = jdbcTemplate.queryForList("select t_role.id as id, t_role.rolename as rolename from t_role "
				+ "inner join t_user_role on t_role.id=t_user_role.role_id "
				+ "where t_user_role.user_id=?", user.getId());
		List<Role> roles = new ArrayList<>();
		Role role = null;
		for (Map<String, Object> _result : listResult) {
			role = new Role();
			role.setId((int)_result.get("id"));
			role.setRolename((String)_result.get("rolename"));
			roles.add(role);
		}
		
		user.setRoles(roles);
		return user;
	}
}

4.下面是最重要的Service部分,需要继承UserDetailService接口

/**
 * 使用数据库认证的关键在与实现UserDetailsService接口,重写loadUserByUsername方法
 * 
 * @author Administrator
 *
 */
@Service("appUserDetailsService")
public class AppUserDetailsServiceImpl implements UserDetailsService {

	@Autowired
	private UserDao userDao;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		// 获取用户
		User user = userDao.queryUserByName(username);
		
		// 添加权限
		List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
		SimpleGrantedAuthority simpleGrantedAuthority = null;
		for (Role role : user.getRoles()) {
			simpleGrantedAuthority = new SimpleGrantedAuthority(role.getRolename());
			grantedAuthorities.add(simpleGrantedAuthority);
		}

		return new org.springframework.security.core.userdetails.User(
				user.getUsername(), user.getPassword(), grantedAuthorities);
	}

}

5.下面是控制器部分

@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@RequestMapping(value = { "/", "/welcome" }, method = RequestMethod.GET)
	public ModelAndView welcomePage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security");
		model.addObject("message", "This is welcome page!");
		model.setViewName("hello");
		return model;
	}

	@RequestMapping(value = "/admin", method = RequestMethod.GET)
	public ModelAndView adminPage() {
		System.out.println("sessionID:" + ((WebAuthenticationDetails)SecurityContextHolder.getContext().getAuthentication().getDetails()).getSessionId() + 
				" User:" + ((UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername() + 
				" current thread:" + Thread.currentThread().getName() + 
				" isAuthenticated:" + SecurityContextHolder.getContext().getAuthentication().isAuthenticated());	
		System.out.println("authority list:");
		for (GrantedAuthority authorityGranter : SecurityContextHolder.getContext().getAuthentication().getAuthorities()) {
			System.out.println(authorityGranter.getAuthority());
		}
		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security");
		model.addObject("message", "This is admin page!");
		model.setViewName("admin");
		return model;
	}
	
	@RequestMapping(value = "/user", method = RequestMethod.GET)
	public ModelAndView userPage() {
		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security");
		model.addObject("message", "This is user page!");
		model.setViewName("admin");
		return model;
	}
}

至此Java代码结束了,下面是xml配置部分

6.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">

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- SpringSecuity配置 -->
	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<!-- SpringMVC 字符编码过滤 -->
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
</web-app>

7.SpringMVC配置部分

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="com.security.demo.controller"/>
	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>

</beans:beans>

8.数据库配置(最简单的配置,直接使用jdbctemplate,demo为了简单起见)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!--数据源的配置 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///testdb"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    
    
    <!-- jdbc template -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

</beans>

9.下面是Spring-Security的配置,注意<authentication-provider>需要使用user-service-ref引用我们之前自定义的查询用户信息的service,如果要自定义<authentication-provider>也可以直接使用ref引用自定义的authenticationProvider即可(spring Security默认情况下使用的是DaoAuthenticationProvider)

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- 静态资源不需要安全验证 -->
	<http pattern="/resources/**" security="none"/>

	<http auto-config="true" use-expressions="false">
		<intercept-url pattern="/admin**" access="ROLE_ADMIN" />
		<intercept-url pattern="/user**" access="ROLE_USER" />
		<csrf disabled="true"/>
		<!-- HTTP基本认证 
		<http-basic/> -->
		<!-- 通过form-login设置自定义的登陆界面
		<form-login/> -->
		<!-- 通过logout设置退出登陆地址等
		<logout/> -->
	</http>

	<authentication-manager> 	
	  	<!-- 引用自定义的service来替换默认的UserDetailsService -->
	  	<authentication-provider user-service-ref="appUserDetailsService"></authentication-provider>
	</authentication-manager>

</beans:beans>

10.最后是三个JSP文件,分别是:admin.jsp、hello.jsp、home.jsp。注意一下,退出登陆是/logout而不是/j_spring_security_logout

<!-- admin.jsp -->
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<body>
	<h1>标题: ${title}</h1>
	<h1>消息 : ${message}</h1>

	<c:if test="${pageContext.request.userPrincipal.name != null}">
	   <h2>欢迎: ${pageContext.request.userPrincipal.name} 
           | <!-- spring security 4中退出已经修改为/logout --><a href="<c:url value="/logout" />" > Logout</a></h2>  
	</c:if>
</body>
</html> 
<!-- hello.jsp -->
<%@page session="false"%>
<html>
<body>
	<h1>标题: ${title}</h1>	
	<h1>消息 : ${message}</h1>	
</body>
</html>
<!-- home.jsp -->
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
	<title>Home</title>
</head>
<body>
<h1>
	Hello world!  
</h1>

<P>  The time on the server is ${serverTime}. </P>
</body>
</html>

下面展示一下项目结构:

七、参考

《Spring In Action(第四版)》

http://www.cnblogs.com/wutianqi/p/9185266.html

http://www.cnblogs.com/wutianqi/p/9186645.html

https://www.cnblogs.com/jaylon/p/4905769.html

https://www.cnblogs.com/guoziyi/p/6001085.html

https://www.cnblogs.com/xz816111/p/8528896.html

https://blog.youkuaiyun.com/elim168/article/details/72869685

https://blog.youkuaiyun.com/kaikai8552/article/details/3930747

https://blog.youkuaiyun.com/lbqssss/article/details/78971037

https://blog.youkuaiyun.com/lifeifei2010/article/details/78787558

https://blog.youkuaiyun.com/marvel__dead/article/details/77094848

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值