Spring boot+Spring Security 4配置整合实例
本例所覆盖的内容:
1. 使用Spring Security管理用户身份认证、登录退出
2. 用户密码加密及验证
3. 采用数据库的方式实现Spring Security的remember-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")<u>
- testCompile("org.springframework.boot:spring-boot-starter-test")
- testCompile("org.springframework.security:spring-security-test")
使用Spring Security4的四种方法概述
那么在Spring Security4的使用中,有4种方法:
- 一种是全部利用配置文件,将用户、权限、资源(url)硬编码在xml文件中;
- 二种是用户和权限用数据库存储,而资源(url)和权限的对应采用硬编码配置。
- 三种是细分角色和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义过滤器,代替原有的FilterSecurityInterceptor过滤器 并分别实现AccessDecisionManager、InvocationSecurityMetadataSourceService和UserDetailsService,并在配置文件中进行相应配置。
- 四是修改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 的工作机制:
下面我们就来根据这个图中标注出的,重要的几个4个类来配置吧!
- 当然要现在application.properties配置文件中配置好数据库。
- 开始配置相关的实体类 SysUser.java SRole.java SysResource.java SysResourceRole.java
- 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<SysRole> SysRoles = new HashSet<SysRole>(0);// 所对应的角色集合
- public SysUser() {
- }
- public SysUser(String name, String email, String password, Date dob, Set<SysRole> 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<SysRole> getSysRoles() {
- return this.SysRoles;
- }
- public void setSRoles(Set<SysRole> 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会自动创建表。在表中插入几条测试数据:- s_user表
- s_role表
6.s_resource表
- <span style="color:rgb(51,204,0);">//请勿手工写入数据 供remember-me功能使用</span>
- 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<span style="font-family:Arial, Helvetica, sans-serif;">;</span>
- 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<? extends GrantedAuthority> getAuthorities() {
- Collection<GrantedAuthority> authorities = new ArrayList<>();
- Set<SysRole> 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<SysRole> 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;
- }
- }
- 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
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
- <head>
- <title>Spring Security Example</title>
- </head>
- <body>
- <h1>Welcome!</h1>
- <p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
- </body>
- </html>
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
- xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
- <head>
- <title>Hello World!</title>
- </head>
- <body>
- <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
- <form th:action="@{/logout}" method="post">
- <input type="submit" value="Sign Out"/>
- </form>
- </body>
- </html>
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
- xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
- <head>
- <title>Spring Security Example </title>
- </head>
- <body>
- <div th:if="${param.error}">
- Invalid username and password.
- </div>
- <div th:if="${param.logout}">
- You have been logged out.
- </div>
- <form th:action="@{/login}" method="post">
- <div><label> User Name : <input type="text" name="username"/> </label></div>
- <div><label> Password: <input type="password" name="password"/> </label></div>
- <div><input type="submit" value="Sign In"/></div>
- <input type="checkbox" name="remember-me" value="true" th:checked="checked"/><p>Remember me</p>
- </form>
- </body>
- </html>
- 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<String, Collection<ConfigAttribute>> resourceMap = null;
- /*public CustomInvocationSecurityMetadataSourceService(SResourceService sres,SRoleService sR) {
- this.sResourceService = sres;
- this.sRoleService = sR;
- loadResourceDefine();
- }*/
- @PostConstruct<span style="color:#33cc00;">//<span style="font-family:Helvetica, Tahoma, Arial, sans-serif;font-size:14px;line-height:25.1875px;"> </span><span style="font-family:Helvetica, Tahoma, Arial, sans-serif;line-height:25.1875px;"><span style="font-size:10px;">被@PostConstruct修饰的方法会在服务器加载Servle的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。</span></span></span>
- private void loadResourceDefine() { <span style="color:#33cc00;"> //一定要加上<span style="font-family:Arial, Helvetica, sans-serif;">@PostConstruct注解</span></span>
- <span style="color:#33cc00;">// 在Web服务器启动时,提取系统中的所有权限。</span>
- List<Map<String,Object>> list =sRoleVODao.findAll();
- List<String> query = new ArrayList<String>();
- if(list!=null && list.size()>0) {
- for(Map<String,Object> sr :list){
- //String name = sr.get("name")
- Object value = sr.get("name");
- String name = String.valueOf(value);
- query.add(name);
- }
- }
- <span style="color:#33cc00;">/*
- * 应当是资源为key, 权限为value。 资源通常为url, 权限就是那些以ROLE_为前缀的角色。 一个资源可以由多个权限来访问。
- * sparta
- */</span>
- resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
- for (String auth : query) {
- ConfigAttribute ca = new SecurityConfig(auth);
- //List<Map<String,Object>> query1 = sResourceVODao.findByRoleName(auth);
- List<String> query1 = new ArrayList<String>();
- List<Map<String, Object>> list1 = sResourceVODao.findByRoleName(auth);
- if(list1!=null && list1.size()>0) {
- for(Map<String, Object> map :list1){
- Object value = map.get("resource_string");
- String url = String.valueOf(value);
- query1.add(url);
- }
- }
- for (String res : query1) {
- String url = res;
- <span style="color:#33cc00;">/*
- * 判断资源文件和权限的对应关系,如果已经存在相关的资源url,则要通过该url为key提取出权限集合,将权限增加到权限集合中。
- * sparta
- */</span>
- if (resourceMap.containsKey(url)) {
- Collection<ConfigAttribute> value = resourceMap.get(url);
- value.add(ca);
- resourceMap.put(url, value);
- } else {
- Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
- atts.add(ca);
- resourceMap.put(url, atts);
- }
- }
- }
- }
- @Override
- public Collection<ConfigAttribute> getAllConfigAttributes() {
- return new ArrayList<ConfigAttribute>();
- }
- <span style="color:#33cc00;">
- // 根据URL,找到相关的权限配置。</span>
- @Override
- public Collection<ConfigAttribute> getAttributes(Object object)
- throws IllegalArgumentException {
- System.out.println("nwuidhwuiehdfu");
- <span style="color:#33cc00;">// object 是一个URL,被用户请求的url。</span>
- FilterInvocation filterInvocation = (FilterInvocation) object;
- if (resourceMap == null) {
- loadResourceDefine();
- }
- Iterator<String> 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;
- }
- }
- 最核心的地方,就是提供某个资源对应的权限定义,即getAttributes方法返回的结果。 此类在初始化时,应该取到所有资源及其对应角色的定义。
*/
@Service
public class CustomInvocationSecurityMetadataSourceService implements
FilterInvocationSecurityMetadataSource {
@Autowired
private SResourceVODao sResourceVODao;
@Autowired
private SRoleVODao sRoleVODao;
private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
/*public CustomInvocationSecurityMetadataSourceService(SResourceService sres,SRoleService sR) {
this.sResourceService = sres;
this.sRoleService = sR;
loadResourceDefine();
}*/
@PostConstruct<span style="color:#33cc00;">//<span style="font-family:Helvetica, Tahoma, Arial, sans-serif;font-size:14px;line-height:25.1875px;"> </span><span style="font-family:Helvetica, Tahoma, Arial, sans-serif;line-height:25.1875px;"><span style="font-size:10px;">被@PostConstruct修饰的方法会在服务器加载Servle的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。</span></span></span>
private void loadResourceDefine() { <span style="color:#33cc00;"> //一定要加上<span style="font-family:Arial, Helvetica, sans-serif;">@PostConstruct注解</span></span>
<span style="color:#33cc00;">// 在Web服务器启动时,提取系统中的所有权限。</span>
List<Map<String,Object>> list =sRoleVODao.findAll();
List<String> query = new ArrayList<String>();
if(list!=null && list.size()>0) {
for(Map<String,Object> sr :list){
//String name = sr.get("name")
Object value = sr.get("name");
String name = String.valueOf(value);
query.add(name);
}
}
<span style="color:#33cc00;">/*
* 应当是资源为key, 权限为value。 资源通常为url, 权限就是那些以ROLE_为前缀的角色。 一个资源可以由多个权限来访问。
* sparta
*/</span>
resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
for (String auth : query) {
ConfigAttribute ca = new SecurityConfig(auth);
//List<Map<String,Object>> query1 = sResourceVODao.findByRoleName(auth);
List<String> query1 = new ArrayList<String>();
List<Map<String, Object>> list1 = sResourceVODao.findByRoleName(auth);
if(list1!=null && list1.size()>0) {
for(Map<String, Object> map :list1){
Object value = map.get("resource_string");
String url = String.valueOf(value);
query1.add(url);
}
}
for (String res : query1) {
String url = res;
<span style="color:#33cc00;">/*
* 判断资源文件和权限的对应关系,如果已经存在相关的资源url,则要通过该url为key提取出权限集合,将权限增加到权限集合中。
* sparta
*/</span>
if (resourceMap.containsKey(url)) {
Collection<ConfigAttribute> value = resourceMap.get(url);
value.add(ca);
resourceMap.put(url, value);
} else {
Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
atts.add(ca);
resourceMap.put(url, atts);
}
}
}
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return new ArrayList<ConfigAttribute>();
}
// 根据URL,找到相关的权限配置。
@Override
public Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException {
System.out.println("nwuidhwuiehdfu");
// object 是一个URL,被用户请求的url。
FilterInvocation filterInvocation = (FilterInvocation) object;
if (resourceMap == null) {
loadResourceDefine();
}
Iterator<String> 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;
}
}
- /*
- * @(#) 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;
- <span style="color:#33cc00;">/**
- *AccessdecisionManager在Spring security中是很重要的。
- *
- *在验证部分简略提过了,所有的Authentication实现需要保存在一个GrantedAuthority对象数组中。
- *这就是赋予给主体的权限。 GrantedAuthority对象通过AuthenticationManager
- *保存到 Authentication对象里,然后从AccessDecisionManager读出来,进行授权判断。
- *
- *Spring Security提供了一些拦截器,来控制对安全对象的访问权限,例如方法调用或web请求。
- *一个是否允许执行调用的预调用决定,是由AccessDecisionManager实现的。
- *这个 AccessDecisionManager 被AbstractSecurityInterceptor调用,
- *它用来作最终访问控制的决定。 这个AccessDecisionManager接口包含三个方法:
- *
- void decide(Authentication authentication, Object secureObject,
- List<ConfigAttributeDefinition> 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支持安全对象的类型。
- */</span>
- @Service
- public class CustomAccessDecisionManager implements AccessDecisionManager {
- public void decide( Authentication authentication, Object object,
- Collection<ConfigAttribute> configAttributes)
- throws AccessDeniedException, InsufficientAuthenticationException{
- if( configAttributes == null ) {
- return ;
- }
- Iterator<ConfigAttribute> ite = configAttributes.iterator();
- while( ite.hasNext()){
- ConfigAttribute ca = ite.next();
- String needRole = ((SecurityConfig)ca).getAttribute();
- <span style="color:#33cc00;"> //ga 为用户所被赋予的权限。 needRole 为访问相应的资源应该具有的权限。</span>
- for( GrantedAuthority ga: authentication.getAuthorities()){
- if(needRole.trim().equals(ga.getAuthority().trim())){
- return;
- }
- }
- }
- throw new AccessDeniedException("权限不足");
- }
- public boolean supports( ConfigAttribute attribute ){
- <span style="color:#ff0000;">return true;</span><span style="color:#33cc00;">//都要设为true</span>
- }
- public boolean supports(Class<?> clazz){
- <span style="color:#ff0000;">return true;</span><span style="color:rgb(51,204,0);font-family:Arial, Helvetica, sans-serif;">//都要设为true</span>
- }
- }
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<? extends Object> 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===========================");
- }
- }
- 该过滤器的主要作用就是通过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<? extends Object> 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。