一、前言
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