Spring boot+Spring Security 4配置整合实例

本例所覆盖的内容:

1. 使用Spring Security管理用户身份认证、登录退出

2. 用户密码加密及验证

3. 采用数据库的方式实现Spring Securityremember-me功能

4. 获取登录用户信息。

5.使用Spring Security管理url和权限

 

本例所使用的框架:

1. Spring boot

2. Spring MVC

3. Spring Security

4. Spring Data JPA

5. thymeleaf

6.gradle

一、 整合Spring Security

build.gradle中加入如下片段:

    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.springframework.boot:spring-boot-starter-security")     testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.springframework.security:spring-security-test")

使用Spring Security4的四种方法概述

    那么在Spring Security4的使用中,有4种方法:

  1.     一种是全部利用配置文件,将用户、权限、资源(url)硬编码在xml文件中;
  2.     二种是用户和权限用数据库存储,而资源(url)和权限的对应采用硬编码配置。
  3.     三种是细分角色和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义过滤器,代替原有的FilterSecurityInterceptor过滤器         并分别实现AccessDecisionManager、InvocationSecurityMetadataSourceService和UserDetailsService,并在配置文件中进行相应配置。
  4.     四是修改spring security的源代码,主要是修改InvocationSecurityMetadataSourceService和UserDetailsService两个类。 前者是将配置文件     或数据库中存储的资源(url)提取出来加工成为url和权限列表的Map供Security使用,后者提取用户名和权限组成一个完整的(UserDetails)User     对象,该对象可以提供用户的详细信息供AuthentationManager进行认证与授权使用。

我们今天来实现一下第三种。

 当然,spring security4毕竟是西方国家的东西,以英文为主,使用习惯和文化的差异共存,况且为了适应大多数Web应用的权限管理,作者将Spring Security4打造的精简而灵活。精简指Spring Security4对用户和权限的表设计的非常简单,并且没有采用数据库来管理资源(URL)。这样的话,对于我们国人用户来说,是个很大的遗憾,这个遗憾甚至能够影响到我们对安全框架的选型。你想啊,在国内大多数项目中,均设置了比较复杂的权限控制,一般就会涉及到用户、角色、资源3张表,若要加上3张表之间的对应关系表2张,得有5张表。

    但是,Spring Security4提供了灵活的扩展方法。具体应该扩展哪些类呢? 或者到底Spring Security3工作的流程如何,你不妨参看下面一篇文章,就会获得
一些启示,网址为:http://www.blogjava.net/SpartaYew/archive/2011/06/15/350630.html, 哈哈,谢谢分享。

还有一个地址很有价值,http://download.csdn.net/detail/muddled/8981809,我就参考着上面的介绍扩展了4个类。

首先来说一下第三种方法的实现流程,我画了一张简易版流程图,帮助大家理解spring security4 的工作机制:

Spring boot+Spring Security 4配置整合实例_第1张图片

下面我们就来根据这个图中标注出的,重要的几个4个类来配置吧!

  1. 当然要现在application.properties配置文件中配置好数据库。
  2. 开始配置相关的实体类 SysUser.java  SRole.java  SysResource.java SysResourceRole.java
  3. package security.entity;
    
    import java.util.Date;
    import java.util.HashSet;
    import java.util.Set;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.OneToMany;
    import javax.persistence.Table;
    import javax.persistence.Temporal;
    import javax.persistence.TemporalType;
    	@Entity
    	@Table(name = "s_user")//code11
    	public class SysUser implements java.io.Serializable {
    		
    		@Id
    		@GeneratedValue(strategy = GenerationType.IDENTITY)
    		@Column(name = "id", unique = true, nullable = false)
    		private Integer id;
    		@Column(name = "name", length = 120)
    		private String name; //用户名
    		@Column(name = "email", length = 50)
    		private String email;//用户邮箱
    		@Column(name = "password", length = 120)
    		private String password;//用户密码
    		@Temporal(TemporalType.DATE)
    		@Column(name = "dob", length = 10)
    		private Date dob;//时间
    		
    		@OneToMany(fetch = FetchType.EAGER, mappedBy = "SUser")
    		private Set SysRoles = new HashSet(0);// 所对应的角色集合
    
    		public SysUser() {
    		}
    
    		public SysUser(String name, String email, String password, Date dob, Set SysRoles) {
    			this.name = name;
    			this.email = email;
    			this.password = password;
    			this.dob = dob;
    			this.SysRoles = SysRoles;
    		}
    
    		
    		public Integer getId() {
    			return this.id;
    		}
    
    		public void setId(Integer id) {
    			this.id = id;
    		}
    
    		
    		public String getName() {
    			return this.name;
    		}
    
    		public void setName(String name) {
    			this.name = name;
    		}
    
    		public String getEmail() {
    			return this.email;
    		}
    
    		public void setEmail(String email) {
    			this.email = email;
    		}
    
    		public String getPassword() {
    			return this.password;
    		}
    
    		public void setPassword(String password) {
    			this.password = password;
    		}
    
    		
    		public Date getDob() {
    			return this.dob;
    		}
    
    		public void setDob(Date dob) {
    			this.dob = dob;
    		}
    
    		@OneToMany(fetch = FetchType.EAGER, mappedBy = "SUser")
    		public Set getSysRoles() {
    			return this.SysRoles;
    		}
    
    		public void setSRoles(Set SysRoles) {
    			this.SysRoles = SysRoles;
    		}
    
    }
    

    package security.entity;
    
    import java.util.Date;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.ManyToOne;
    import javax.persistence.Table;
    //角色表
    @Entity
    @Table(name="s_role")
    public class SysRole {
    	@Id
    	@GeneratedValue(strategy=GenerationType.IDENTITY)
    	@Column (name="id",length=10)
    	private int id;
    	
    	@ManyToOne(fetch = FetchType.LAZY)
    	@JoinColumn(name = "uid", nullable = false)
    	private SysUser SUser;//角色对应的用户实体
    	
    	@Column(name="name",length=100)
    	private String name;//角色名称
    	
    	public int getId() {
    		return id;
    	}
    	public void setId(int id) {
    		this.id = id;
    	}
    	
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public SysUser getSUser() {
    		return SUser;
    	}
    	public void setSUser(SysUser sUser) {
    		SUser = sUser;
    	}
    	
    	
    }
    

    package cn.paybay.ticketManager.entity;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    @Entity
    @Table(name="s_resource")
    public class SysResource {
    		@Id
    		@GeneratedValue(strategy=GenerationType.IDENTITY)
    		@Column (name="id",length=10)
    		private int id;
    		
    		@Column(name="resourceString",length=1000)
    		private String resourceString;//url
    		
    		@Column(name="resourceId",length=50)
    		private String resourceId;//资源ID
    		
    		@Column(name="remark",length=200)
    		private String remark;//备注
    		
    		@Column(name="resourceName",length=400)
    		private String resourceName;//资源名称
    		
    		@Column(name="methodName",length=400)
    		private String methodName;//资源所对应的方法名
    		
    		@Column(name="methodPath",length=1000)
    		private String methodPath;//资源所对应的包路径
    		
    		public int getId() {
    			return id;
    		}
    
    		public void setId(int id) {
    			this.id = id;
    		}
    
    		public String getResourceString() {
    			return resourceString;
    		}
    
    		public void setResourceString(String resourceString) {
    			this.resourceString = resourceString;
    		}
    
    		public String getResourceId() {
    			return resourceId;
    		}
    
    		public void setResourceId(String resourceId) {
    			this.resourceId = resourceId;
    		}
    
    		public String getRemark() {
    			return remark;
    		}
    
    		public void setRemark(String remark) {
    			this.remark = remark;
    		}
    
    		public String getResourceName() {
    			return resourceName;
    		}
    
    		public void setResourceName(String resourceName) {
    			this.resourceName = resourceName;
    		}
    
    		public String getMethodName() {
    			return methodName;
    		}
    
    		public void setMethodName(String methodName) {
    			this.methodName = methodName;
    		}
    
    		public String getMethodPath() {
    			return methodPath;
    		}
    
    		public void setMethodPath(String methodPath) {
    			this.methodPath = methodPath;
    		}
    		
    		
    }
    

    package security.entity;
    
    import java.util.Date;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.ManyToOne;
    import javax.persistence.Table;
    @Entity
    @Table(name="s_resource_role")
    public class SysResourceRole {
    		@Id
    		@GeneratedValue(strategy=GenerationType.IDENTITY)
    		@Column (name="id",length=10)
    		private int id;
    		
    		@Column(name="roleId",length=50)
    		private String roleId; //角色ID
    		
    		@Column(name="resourceId",length=50)
    		private String resourceId;//资源ID
    		
    		@Column(name="updateTime")
    		private Date updateTime;//更新时间
    
    		public int getId() {
    			return id;
    		}
    
    		public void setId(int id) {
    			this.id = id;
    		}
    
    		public String getRoleId() {
    			return roleId;
    		}
    
    		public void setRoleId(String roleId) {
    			this.roleId = roleId;
    		}
    
    		public String getResourceId() {
    			return resourceId;
    		}
    
    		public void setResourceId(String resourceId) {
    			this.resourceId = resourceId;
    		}
    
    		public Date getUpdateTime() {
    			return updateTime;
    		}
    
    		public void setUpdateTime(Date updateTime) {
    			this.updateTime = updateTime;
    		}
    
    		
    }
    

    好了实体类都建好了。然后运行一下,springboot程序。hibernate会自动创建表。在表中插入几条测试数据:
  4. s_user表
  5. s_role表
Spring boot+Spring Security 4配置整合实例_第2张图片

     6.s_resource表


      7.s_resource_role表

Spring boot+Spring Security 4配置整合实例_第3张图片

//请勿手工写入数据 供remember-me功能使用
CREATE TABLE `persistent_logins` (
  `username` varchar(64) NOT NULL,
  `series` varchar(64) NOT NULL,
  `token` varchar(64) NOT NULL,
  `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
)

好了,现在我们来配置一下用户和角色的认证吧。

1、首先,创建WebSecurityConfig.java配置类,其中不明所以的地方请参照上面的图,慢慢往下看。

package security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import security.support.CustomUserDetailsService;
import security.support.LoginSuccessHandler;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	private CustomUserDetailsService customUserDetailsService;
	//http://localhost:8080/login 输入正确的用户名密码 并且选中remember-me 则登陆成功,转到 index页面 
	//再次访问index页面无需登录直接访问
	//访问http://localhost:8080/home 不拦截,直接访问,
	//访问http://localhost:8080/hello 需要登录验证后,且具备 “ADMIN”权限hasAuthority("ADMIN")才可以访问
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
		.authorizeRequests()
		.antMatchers("/home").permitAll()//访问:/home 无需登录认证权限
		.anyRequest().authenticated() //其他所有资源都需要认证,登陆后访问
		.antMatchers("/hello").hasAuthority("ADMIN") //登陆后之后拥有“ADMIN”权限才可以访问/hello方法,否则系统会出现“403”权限不足的提示
		.and()
		.formLogin()
		.loginPage("/login")//指定登录页是”/login”
		.permitAll()
		.successHandler(loginSuccessHandler()) //登录成功后可使用loginSuccessHandler()存储用户信息,可选。
		.and()
		.logout()
		.logoutSuccessUrl("/home") //退出登录后的默认网址是”/home”
		.permitAll()
		.invalidateHttpSession(true)
		.and()
		.rememberMe()//登录后记住用户,下次自动登录,数据库中必须存在名为persistent_logins的表
		.tokenValiditySeconds(1209600);
	}

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {	
//指定密码加密所使用的加密器为passwordEncoder()
//需要将密码加密后写入数据库 
	auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
		auth.eraseCredentials(false);		
	}

	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder(4);
	}

	@Bean
	public LoginSuccessHandler loginSuccessHandler(){
		return new LoginSuccessHandler();
	}
}

2、CustomUserDetailsService.java

package security.support;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import security.entity.SysUser;
import security.entity.User;
import security.service.UserService;
@Component
public class CustomUserDetailsService implements UserDetailsService {
	@Autowired  //业务服务类
	private UserService userService;

	@Override
	public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        //SysUser对应数据库中的用户表,是最终存储用户和密码的表,可自定义
        //本例使用SysUser中的name作为用户名:
		SysUser user = userService.findByName(userName);
		if (user == null) {
			throw new UsernameNotFoundException("UserName " + userName + " not found");
		}
		// SecurityUser实现UserDetails并将SysUser的name映射为username
		SecurityUser seu = new SecurityUser(user);
		return  seu;
	}

}

3、SecurityUser.java

package security.support;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import security.entity.SysRole;
import security.entity.SysUser;

public class SecurityUser extends SysUser implements UserDetails {
	private static final long serialVersionUID = 1L;
	public SecurityUser(SysUser suser) {
		if(suser != null)
		{
			this.setId(suser.getId());
			this.setName(suser.getName());
			this.setEmail(suser.getEmail());
			this.setPassword(suser.getPassword());
			this.setDob(suser.getDob());
			this.setSysRoles(suser.getSysRoles());
		}		
	}
	
	@Override
	public Collection getAuthorities() {
		
		Collection authorities = new ArrayList<>();
		Set userRoles = this.getSysRoles();
		
		if(userRoles != null)
		{
			for (SysRole role : userRoles) {
				SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getName());
				authorities.add(authority);
			}
		}
		return authorities;
	}

	@Override
	public String getPassword() {
		return super.getPassword();
	}

	@Override
	public String getUsername() {
		return super.getName();
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}
}

4、LoginSuccessHandler.java

package security.support;

import java.io.IOException;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

import security.entity.SysRole;
import security.entity.SysUser;

public class LoginSuccessHandler extends
		SavedRequestAwareAuthenticationSuccessHandler {
	@Override  
    public void onAuthenticationSuccess(HttpServletRequest request,  
            HttpServletResponse response, Authentication authentication) throws IOException,  
            ServletException {  
        //获得授权后可得到用户信息   可使用SUserService进行数据库操作
        SysUser userDetails = (SysUser)authentication.getPrincipal();  
       /* Set roles = userDetails.getSysRoles();*/
        //输出登录提示信息  
        System.out.println("管理员 " + userDetails.getName() + " 登录");  
    	
        System.out.println("IP :"+getIpAddress(request));
              
        super.onAuthenticationSuccess(request, response, authentication);  
    }  
    
    public String getIpAddress(HttpServletRequest request){    
        String ip = request.getHeader("x-forwarded-for");    
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {    
            ip = request.getHeader("Proxy-Client-IP");    
        }    
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {    
            ip = request.getHeader("WL-Proxy-Client-IP");    
        }    
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {    
            ip = request.getHeader("HTTP_CLIENT_IP");    
        }    
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {    
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");    
        }    
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {    
            ip = request.getRemoteAddr();    
        }    
        return ip;    
    }  
}
5、MvcConfig.java

package security;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    	registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/login").setViewName("login");
    }

}


6、在resource下面创建templates目录,然后放相关的html文件:

home.html



    
        Spring Security Example
    
    
        

Welcome!

Click here to see a greeting.

hello.html


    
        Hello World!
    
    
        

Hello [[${#httpServletRequest.remoteUser}]]!

login.html


    
        Spring Security Example 
    
    
        
Invalid username and password.
You have been logged out.

Remember me

接下来是主类:MainApplication.java

package security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import security.entity.SysUser;
import security.entity.User;
import security.service.UserService;
import security.Appctx;

@SpringBootApplication
public class MainApplication{
	public static void main(String[] args) {
		//SpringApplication.run(MainApplication.class, args);
		SpringApplication app=new SpringApplication(MainApplication.class);		
		
		Appctx.ctx=app.run(args);
		/*UserService suserService = (UserService) Appctx.ctx.getBean("suserService");
		SysUser su= suserService.findByName("TEST");
        BCryptPasswordEncoder bc=new BCryptPasswordEncoder(4);//将密码加密 可以先设置初始密码:000000 
        su.setPassword(bc.encode(su.getPassword()));//然后使用密码为key值进行加密,运行主类后,会自动加密密码,可连接数据库查看。
        System.out.println("密码"+su.getPassword());
        suserService.update(su);//运行一次后记得注释这段重复加密会无法匹配*/
	}
}

Appctx.java

package security.support;

import org.springframework.context.ApplicationContext;

public class Appctx {
	public static ApplicationContext ctx=null; 
    public static Object getObject(String string){
    	return ctx.getBean(string);
    }
}

1. 运行,访问http://localhost:8080/hello,系统出现如下界面:


登陆成功后:

登陆拥有ADMIN权限的用户,可以进入/home

如果用户不具有权限,会出现以下:

好了,根据配置:.antMatchers("/hello").hasAuthority("ADMIN")。来进行权限控制,就到这里,下面,我们来根据数据库中的资源和权限的关系,进行授权和认证


1、CustomInvocationSecurityMetadataSourceService.java 参照流程图

/*
 * @(#) MyInvocationSecurityMetadataSourceService.java  2011-3-23 下午02:58:29
 *
 * Copyright 2011 by Sparta 
 */

package security.support;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import security.dao.SResourceVODao;
import security.dao.SRoleDao;
import security.dao.SRoleVODao;
import security.entity.SRole;
import security.service.SResourceService;
import security.service.SRoleService;

/**
 * 最核心的地方,就是提供某个资源对应的权限定义,即getAttributes方法返回的结果。 此类在初始化时,应该取到所有资源及其对应角色的定义。
 * 
 */
@Service
public class CustomInvocationSecurityMetadataSourceService implements
		FilterInvocationSecurityMetadataSource {
	
	@Autowired
	private SResourceVODao sResourceVODao;
	
	@Autowired
	private SRoleVODao sRoleVODao;
	
	private static Map> resourceMap = null;

	/*public CustomInvocationSecurityMetadataSourceService(SResourceService sres,SRoleService sR) {
		this.sResourceService = sres;
		this.sRoleService = sR;
		loadResourceDefine();
	}*/
	@PostConstruct//  被@PostConstruct修饰的方法会在服务器加载Servle的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
	private void loadResourceDefine() {   //一定要加上@PostConstruct注解
	// 在Web服务器启动时,提取系统中的所有权限。
		List> list =sRoleVODao.findAll();
		List query = new ArrayList();
		if(list!=null && list.size()>0) {
			for(Map sr :list){
				//String name = sr.get("name")	
				Object value = sr.get("name");
				String name = String.valueOf(value);
				query.add(name);
			}
		}
		/* * 应当是资源为key, 权限为value。 资源通常为url, 权限就是那些以ROLE_为前缀的角色。 一个资源可以由多个权限来访问。 * sparta */
		resourceMap = new HashMap>();

		for (String auth : query) {
			ConfigAttribute ca = new SecurityConfig(auth);
			//List> query1 = sResourceVODao.findByRoleName(auth);
			List query1 = new ArrayList();
			List>  list1 = sResourceVODao.findByRoleName(auth);
			if(list1!=null && list1.size()>0) {
				for(Map map :list1){
					Object value = map.get("resource_string");
					String url = String.valueOf(value);
					query1.add(url);
				}
			}
			for (String res : query1) {
				String url = res;
				
				/* * 判断资源文件和权限的对应关系,如果已经存在相关的资源url,则要通过该url为key提取出权限集合,将权限增加到权限集合中。 * sparta */
				if (resourceMap.containsKey(url)) {

					Collection value = resourceMap.get(url);
					value.add(ca);
					resourceMap.put(url, value);
				} else {
					Collection atts = new ArrayList();
					atts.add(ca);
					resourceMap.put(url, atts);
				}

			}
		}

	}

	@Override
	public Collection getAllConfigAttributes() {
		 return new ArrayList();
	}
 // 根据URL,找到相关的权限配置。
	@Override
	public Collection getAttributes(Object object)
			throws IllegalArgumentException {
		System.out.println("nwuidhwuiehdfu");
		// object 是一个URL,被用户请求的url。
		FilterInvocation filterInvocation = (FilterInvocation) object;
		if (resourceMap == null) {
			loadResourceDefine();
		}
		Iterator ite = resourceMap.keySet().iterator();
		while (ite.hasNext()) {
			String resURL = ite.next();
			 RequestMatcher requestMatcher = new AntPathRequestMatcher(resURL);
			    if(requestMatcher.matches(filterInvocation.getHttpRequest())) {
				return resourceMap.get(resURL);
			}
		}

		return null;
	}
	@Override
	public boolean supports(Class arg0) {

		return true;
	}

}
2、CustomAccessDecisionManager.java

/*
 * @(#) MyAccessDecisionManager.java  2011-3-23 下午04:41:12
 *
 * Copyright 2011 by Sparta 
 */

package security.support;

import java.util.Collection;
import java.util.Iterator;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/** *AccessdecisionManager在Spring security中是很重要的。 * *在验证部分简略提过了,所有的Authentication实现需要保存在一个GrantedAuthority对象数组中。 *这就是赋予给主体的权限。 GrantedAuthority对象通过AuthenticationManager *保存到 Authentication对象里,然后从AccessDecisionManager读出来,进行授权判断。 * *Spring Security提供了一些拦截器,来控制对安全对象的访问权限,例如方法调用或web请求。 *一个是否允许执行调用的预调用决定,是由AccessDecisionManager实现的。 *这个 AccessDecisionManager 被AbstractSecurityInterceptor调用, *它用来作最终访问控制的决定。 这个AccessDecisionManager接口包含三个方法: * void decide(Authentication authentication, Object secureObject, List config) throws AccessDeniedException; boolean supports(ConfigAttribute attribute); boolean supports(Class clazz); 从第一个方法可以看出来,AccessDecisionManager使用方法参数传递所有信息,这好像在认证评估时进行决定。 特别是,在真实的安全方法期望调用的时候,传递安全Object启用那些参数。 比如,让我们假设安全对象是一个MethodInvocation。 很容易为任何Customer参数查询MethodInvocation, 然后在AccessDecisionManager里实现一些有序的安全逻辑,来确认主体是否允许在那个客户上操作。 如果访问被拒绝,实现将抛出一个AccessDeniedException异常。 这个 supports(ConfigAttribute) 方法在启动的时候被 AbstractSecurityInterceptor调用,来决定AccessDecisionManager 是否可以执行传递ConfigAttribute。 supports(Class)方法被安全拦截器实现调用, 包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型。 */
@Service
public class CustomAccessDecisionManager implements AccessDecisionManager {
	
	public void decide( Authentication authentication, Object object, 
			Collection configAttributes) 
		throws AccessDeniedException, InsufficientAuthenticationException{
		if( configAttributes == null ) {
			return ;
		}
		
		Iterator ite = configAttributes.iterator();
		
		while( ite.hasNext()){
			ConfigAttribute ca = ite.next();
			String needRole = ((SecurityConfig)ca).getAttribute();
			
		 //ga 为用户所被赋予的权限。 needRole 为访问相应的资源应该具有的权限。
			for( GrantedAuthority ga: authentication.getAuthorities()){
				
				if(needRole.trim().equals(ga.getAuthority().trim())){

					return;
				}
				
			}
			
		}
		
		throw new AccessDeniedException("权限不足");
		
	}
	
	public boolean supports( ConfigAttribute attribute ){
		return true;//都要设为true

	}
	
	public boolean supports(Class clazz){
		return true;//都要设为true
	}
	

}

3、 MyFilterSecurityInterceptor.java

/*
 * @(#) MyFilterSecurityInterceptor.java  2011-3-23 上午07:53:03
 *
 * Copyright 2011 by Sparta 
 */

package security.support;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import security.service.SResourceService;

/**
 * 该过滤器的主要作用就是通过spring著名的IoC生成securityMetadataSource。
 * securityMetadataSource相当于本包中自定义的MyInvocationSecurityMetadataSourceService。
 * 该MyInvocationSecurityMetadataSourceService的作用提从数据库提取权限和资源,装配到HashMap中,
 * 供Spring Security使用,用于权限校验。
 * @author sparta 11/3/29
 *
 */
@Component
public class MySecurityFilter 
	extends AbstractSecurityInterceptor
	implements Filter{
	@Autowired
	private CustomInvocationSecurityMetadataSourceService  mySecurityMetadataSource;
	
	@Autowired
	private CustomAccessDecisionManager myAccessDecisionManager;
	
	@Autowired
	private AuthenticationManager authenticationManager;
	
	
	@PostConstruct
	public void init(){
		super.setAuthenticationManager(authenticationManager);
		super.setAccessDecisionManager(myAccessDecisionManager);
	}
	
	public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain)
	throws IOException, ServletException{
		FilterInvocation fi = new FilterInvocation( request, response, chain );
		invoke(fi);
		
	}

	
	public Class getSecureObjectClass(){
		return FilterInvocation.class;
	}

	
	public void invoke( FilterInvocation fi ) throws IOException, ServletException{
		System.out.println("filter..........................");
		InterceptorStatusToken  token = super.beforeInvocation(fi);
		try{
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		}finally{
			super.afterInvocation(token, null);
		}
		
	}
		
	
	@Override
	public SecurityMetadataSource obtainSecurityMetadataSource(){
		System.out.println("filtergergetghrthetyetyetyetyj");
		return this.mySecurityMetadataSource;
	}
	
	public void destroy(){
		System.out.println("filter===========================end");
	}
	public void init( FilterConfig filterconfig ) throws ServletException{
		System.out.println("filter===========================");
	}
}

接下来修改一个类的 大家注意:
WebSecurityConfig.java

package security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

import security.support.CustomUserDetailsService;
import security.support.LoginSuccessHandler;
import security.support.MySecurityFilter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Autowired
	private MyFilterSecurityInterceptor mySecurityFilter;
	
	@Autowired
	private CustomUserDetailsService customUserDetailsService;
	
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
	 
	return super.authenticationManagerBean();
	 
	}
	//http://localhost:8080/login 输入正确的用户名密码 并且选中remember-me 则登陆成功,转到 index页面 
	//再次访问index页面无需登录直接访问
	//访问http://localhost:8080/home 不拦截,直接访问,
	//访问http://localhost:8080/hello 需要登录验证后,且具备 “ADMIN”权限hasAuthority("ADMIN")才可以访问
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
		.addFilterBefore(mySecurityFilter, FilterSecurityInterceptor.class)//在正确的位置添加我们自定义的过滤器
		.authorizeRequests()
		.antMatchers("/home").permitAll()
		.anyRequest().authenticated()
		//.antMatchers("/hello").hasAuthority("ADMIN")
		.and()
		.formLogin()
		.loginPage("/login")	
		.permitAll()
		.successHandler(loginSuccessHandler())//code3
		.and()
		.logout()
		.logoutSuccessUrl("/home")
		.permitAll()
		.invalidateHttpSession(true)
		.and()
		.rememberMe()
		.tokenValiditySeconds(1209600);
	}
	@Override
	    public void configure(WebSecurity web) throws Exception {
	        super.configure(web);
	}
	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {	
//指定密码加密所使用的加密器为passwordEncoder()
//需要将密码加密后写入数据库 
	auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
//不删除凭据,以便记住用户
		auth.eraseCredentials(false);		
	}
	
	// Code5----------------------------------------------
	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder(4);
	}

	// Code3----------------------------------------------
	@Bean
	public LoginSuccessHandler loginSuccessHandler(){
		return new LoginSuccessHandler();
	}
}

ServletInitializer.java

package security;

import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;

public class ServletInitializer extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(MainApplication.class);
	}
	
	@Override
	public void onStartup(ServletContext servletContext)
	throws ServletException {
	 FilterRegistration.Dynamic openEntityManagerInViewFilter = servletContext.addFilter("openEntityManagerInViewFilter", OpenEntityManagerInViewFilter.class);
	     openEntityManagerInViewFilter.setInitParameter("entityManagerFactoryBeanName","entityManagerFactory");
	     openEntityManagerInViewFilter.addMappingForUrlPatterns(null, false, "/*");
	super.onStartup(servletContext);
	}
}

还有主类需要修改

package security;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import security.entity.SysResource;
import security.service.SResourceService;
import csecurity.service.UserService;
import security.support.MySecurityFilter;

@SpringBootApplication
@EnableAutoConfiguration(exclude = MyFilterSecurityInterceptor.class) //注意
public class MainApplication{
	
	@Autowired
	private SResourceService sresourceService;
	
	private static final Logger log = LoggerFactory.getLogger(MainApplication.class);
	@PostConstruct
	 public void initApplication() throws IOException {
	     log.info("Running with Spring profile(s) : {}"); 
	}
	 
	public static void main(String[] args) {
		//SpringApplication.run(MainApplication.class, args);
		SpringApplication app=new SpringApplication(MainApplication.class);		
		Appctx.ctx=app.run(args);
		/*UserService suserService = (UserService) Appctx.ctx.getBean("suserService");
		SysUser su= suserService.findByName("user");
        System.out.println("密码"+su.getPassword());
        System.out.println("名字"+su.getName());
        BCryptPasswordEncoder bc=new BCryptPasswordEncoder(4);//将密码加密
        su.setPassword(bc.encode(su.getPassword()));
        System.out.println("密码"+su.getPassword());
        suserService.update(su);*/
}
}

至此,我们spring security4就集成成功了,代码,博主已经验证,如有什么错误的地方,欢迎提出。谢谢!
还有资源表中的url可以使用spring的RequestMappingHandlerMapping类自动扫描入库,具体请见 http://blog.csdn.net/code__code/article/details/53886912。

你可能感兴趣的:(java,spring,security,springboot,spring,spring,spring,security)