一、什么是SpringSecurity

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。 参考百度百科:https://baike.baidu.com/item/spring security/8831652?fr=aladdin

SpringSecurity官网:https://spring.io/projects/spring-security

二、应用场景

Security在很多企业中作为后台角色权限框架、授权认证oauth2.0 、安全防护(防止跨站点请求)、Session攻击、非常容易融合SpringMVC使用等

三、环境搭建

实现效果

admin账户 所有请求都有权限访问 userAdd账户 只能访问查询和添加订单

spring-security两种模式 formLogin模式 : 表单提交认证模式 (应用广泛) httpBasic模式 :浏览器与服务器做认证授权

关于httpBasic模式:

在HTTP协议进行通信的过程中,HTTP协议定义了基本认证过程以允许HTTP服务器对WEB浏览器进行用户身份证的方法,当一个客户端向HTTP服务 器进行数据请求时,如果客户端未被认证,则HTTP服务器将通过基本认证过程对客户端的用户名及密码进行验证,以决定用户是否合法。客户端在接收到HTTP服务器的身份认证要求后,会提示用户输入用户名及密码,然后将用户名及密码以BASE64加密,加密后的密文将附加于请求信息中, 如当用户名为toov5,密码为:123456时,客户端将用户名和密码用“:”合并,并将合并后的字符串用BASE64加密为密文,并于每次请求数据 时,将密文附加于请求头(Request Header)中。HTTP服务器在每次收到请求包后,根据协议取得客户端附加的用户信息(BASE64加密的用户名和密码),解开请求包,对用户名及密码进行验证,如果用 户名及密码正确,则根据客户端请求,返回客户端所需要的数据;否则,返回错误代码或重新要求客户端提供用户名及密码。

RBAC权限模型:

基于角色的权限访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注。在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。在一个组织中,角色是为了完成各种工作而创造,用户则依据它的责任和资格来被指派相应的角色,用户可以很容易地从一个角色被指派到另一个角色。角色可依新的需求和系统的合并而赋予新的权限,而权限也可根据需要而从某角色中回收。角色与角色的关系可以建立起来以囊括更广泛的客观情况。

httpStatus:状态码 401:未授权 403:权限不足

maven依赖:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>
    <!-- 管理依赖 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.M7</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- springboot整合freemarker -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <!-->spring-boot 整合security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- springboot 整合mybatis框架 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- alibaba的druid数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.9</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

    </dependencies>
    <!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

controller:

@Controller
public class OrderController {
    // 首页
    @RequestMapping("/")
    public String index() {
        return "index";
    }

    // 查询订单
    @RequestMapping("/showOrder")
    public String showOrder() {
        return "showOrder";
    }

    // 添加订单
    @RequestMapping("/addOrder")
    public String addOrder() {
        return "addOrder";
    }

    // 修改订单
    @RequestMapping("/updateOrder")
    public String updateOrder() {
        return "updateOrder";
    }

    // 删除订单
    @RequestMapping("/deleteOrder")
    public String deleteOrder() {
        return "deleteOrder";
    }

    // 自定义登陆页面
    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

对于403错误码的处理:

@Controller
public class ErrorController {

	@RequestMapping("/error/403")
	public String error() {
		return "/error/403";
	}

}

config:拦截请求路径与权限的配置: 对于fromLogin登录页面的修改自定义 关闭csdrf 配置loginpage就OK了

这里查询认证用户与对应权限都是通过数据库来进行查询。

@Component
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAuthenticationSuccessHandler successHandler;
    @Autowired
    private MyAuthenticationFailureHandler failureHandler;
    @Autowired
    private MyUserDetailService myUserDetailService;
    @Autowired
    private PermissionMapper permissionMapper;

    //用户认证信息
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        //设置用户账号信息和权限
//        auth.inMemoryAuthentication().withUser("admin").password("123456").authorities("addOrder","showOrder","updateOrder","deleteOrder");
//        //添加userAdd账号
//        auth.inMemoryAuthentication().withUser("userAdd").password("123456").authorities("showOrder","addOrder");
        auth.userDetailsService(myUserDetailService).passwordEncoder(new PasswordEncoder() {
            //对表单密码进行加密
            public String encode(CharSequence rawPassword) {
//                System.err.println("form ..."+rawPassword);
                return MD5Util.encode((String)rawPassword);
            }

            //加密的密码与数据库密码进行比对
            //rawPassword 表单密码     encodedPassword数据库密码
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                System.out.println("rawPassword:"+rawPassword+",encodedPassword:"+encodedPassword);
                //返回true代表认证成功,false失败
                return MD5Util.encode((String)rawPassword).equals(encodedPassword);
            }
        });
    }

    //配置httpSecurity拦截资源
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();
//        http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().formLogin();
        //如何进行权限控制 给每一个请求路径分配一个权限名称,然后账号只要关联该名称,就可以有访问权限
//        http.authorizeRequests()
//                //配置相关权限
//                .antMatchers("/showOrder").hasAnyAuthority("showOrder")
//                .antMatchers("/addOrder").hasAnyAuthority("addOrder")
//                .antMatchers("/updateOrder").hasAnyAuthority("updateOrder")
//                .antMatchers("/deleteOrder").hasAnyAuthority("deleteOrder")
//                //自定义登录界面,不拦截登录请求
//                .antMatchers("/login").permitAll()
//                .antMatchers("/**").fullyAuthenticated().and().formLogin().loginPage("/login")
//                .successHandler(successHandler)
                .failureHandler(failureHandler)
//                .and().csrf().disable();
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http.authorizeRequests();
        List<Permission> permissionList = permissionMapper.findAllPermission();
        if (permissionList != null && permissionList.size() > 0) {
            permissionList.stream().forEach(permission -> {
                //设置权限
                authorizeRequests.antMatchers(permission.getUrl()).hasAnyAuthority(permission.getPermTag());
            });
        }
        authorizeRequests.antMatchers("/login").permitAll()
                .antMatchers("/**").fullyAuthenticated().and().formLogin().loginPage("/login").successHandler(successHandler).failureHandler(failureHandler)
                .and().csrf().disable();
    }

//    @Bean
//    public static NoOpPasswordEncoder passwordEncoder() {
//        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
//    }
}

配置默认错误页面:

/**
 * 自定义 WEB 服务器参数 可以配置默认错误页面
 */
@Configuration
public class WebServerAutoConfiguration {
	@Bean
	public ConfigurableServletWebServerFactory webServerFactory() {
		TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
		ErrorPage errorPage400 = new ErrorPage(HttpStatus.BAD_REQUEST, "/error/400");
		ErrorPage errorPage401 = new ErrorPage(HttpStatus.UNAUTHORIZED, "/error/401");
		ErrorPage errorPage403 = new ErrorPage(HttpStatus.FORBIDDEN, "/error/403");
		ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
		ErrorPage errorPage415 = new ErrorPage(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "/error/415");
		ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");
		factory.addErrorPages(errorPage400, errorPage401, errorPage403, errorPage404, errorPage415, errorPage500);
		return factory;
	}
}

数据库相关配置:sql脚本:

/*
Navicat MySQL Data Transfer

Source Server         : localhost
Source Server Version : 50556
Source Host           : localhost:3306
Source Database       : rbac_db

Target Server Type    : MYSQL
Target Server Version : 50556
File Encoding         : 65001

Date: 2018-11-13 18:29:33
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
  `id` int(10) NOT NULL,
  `permName` varchar(50) DEFAULT NULL,
  `permTag` varchar(50) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL COMMENT '请求url',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES ('1', '查询订单', 'showOrder', '/showOrder');
INSERT INTO `sys_permission` VALUES ('2', '添加订单', 'addOrder', '/addOrder');
INSERT INTO `sys_permission` VALUES ('3', '修改订单', 'updateOrder', '/updateOrder');
INSERT INTO `sys_permission` VALUES ('4', '删除订单', 'deleteOrder', '/deleteOrder');

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
  `id` int(10) NOT NULL,
  `roleName` varchar(50) DEFAULT NULL,
  `roleDesc` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', 'admin', '管理员');
INSERT INTO `sys_role` VALUES ('2', 'add_user', '添加管理员');

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
  `role_id` int(10) DEFAULT NULL,
  `perm_id` int(10) DEFAULT NULL,
  KEY `FK_Reference_3` (`role_id`),
  KEY `FK_Reference_4` (`perm_id`),
  CONSTRAINT `FK_Reference_4` FOREIGN KEY (`perm_id`) REFERENCES `sys_permission` (`id`),
  CONSTRAINT `FK_Reference_3` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES ('1', '1');
INSERT INTO `sys_role_permission` VALUES ('1', '2');
INSERT INTO `sys_role_permission` VALUES ('1', '3');
INSERT INTO `sys_role_permission` VALUES ('1', '4');
INSERT INTO `sys_role_permission` VALUES ('2', '1');
INSERT INTO `sys_role_permission` VALUES ('2', '2');

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
  `id` int(10) NOT NULL,
  `username` varchar(50) DEFAULT NULL,
  `realname` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  `createDate` date DEFAULT NULL,
  `lastLoginTime` date DEFAULT NULL,
  `enabled` int(5) DEFAULT NULL,
  `accountNonExpired` int(5) DEFAULT NULL,
  `accountNonLocked` int(5) DEFAULT NULL,
  `credentialsNonExpired` int(5) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', 'admin', '张三', '15a013bcac0c50049356b322e955035e\r\n', '2018-11-13', '2018-11-13', '1', '1', '1', '1');
INSERT INTO `sys_user` VALUES ('2', 'userAdd', '小余', '15a013bcac0c50049356b322e955035e\r\n', '2018-11-13', '2018-11-13', '1', '1', '1', '1');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
  `user_id` int(10) DEFAULT NULL,
  `role_id` int(10) DEFAULT NULL,
  KEY `FK_Reference_1` (`user_id`),
  KEY `FK_Reference_2` (`role_id`),
  CONSTRAINT `FK_Reference_2` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`),
  CONSTRAINT `FK_Reference_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `sys_user_role` VALUES ('2', '2');

实体类Entity: 用户信息表的字段都是框架底层封装好的,所以要进行对应,必须实现UserDetails接口

// 用户信息表
@Data
public class User implements UserDetails {

	/*用户ID*/
	private Integer id;
	/*用户名称*/
	private String username;
	/*真实姓名*/
	private String realname;
	/*密码*/
	private String password;
	/*创建日期*/
	private Date createDate;
	/*最后登录时间*/
	private Date lastLoginTime;
	/*是否可用*/
	private boolean enabled;
	/*是否过期*/
	private boolean accountNonExpired;
	/*是否锁定*/
	private boolean accountNonLocked;
	/*证书是否过期*/
	private boolean credentialsNonExpired;
	// 用户所有权限
	private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

	public Collection<? extends GrantedAuthority> getAuthorities() {
		return authorities;
	}
}


// 角色信息表
@Data
public class Role {
	private Integer id;
	private String roleName;
	private String roleDesc;
}


@Data
public class Permission {
	private Integer id;
	// 权限名称
	private String permName;
	// 权限标识
	private String permTag;
	// 请求url
	private String url;
}

mapper:

public interface UserMapper {
	// 查询用户信息
	@Select(" select * from sys_user where username = #{userName}")
	User findByUsername(@Param("userName") String userName);

	// 查询用户的权限
	@Select(" select permission.* from sys_user user" + " inner join sys_user_role user_role"
			+ " on user.id = user_role.user_id inner join "
			+ "sys_role_permission role_permission on user_role.role_id = role_permission.role_id "
			+ " inner join sys_permission permission on role_permission.perm_id = permission.id where user.username = #{userName};")
	List<Permission> findPermissionByUsername(@Param("userName") String userName);
}


public interface PermissionMapper {

	// 查询苏所有权限
	@Select(" select * from sys_permission ")
	List<Permission> findAllPermission();

}

AuthenticationFailureHandler 认证失败接口AuthenticationSuccessHandler 认证成功接口

成功和失败的处理:成功:

@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

	// 用户认证成功
	public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res, Authentication auth)
			throws IOException, ServletException {
		System.out.println("用户登陆成功");
		res.sendRedirect("/");
	}
}

失败:

@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

	public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse res, AuthenticationException auth)
			throws IOException, ServletException {
		System.out.println("用户认证失败");
		res.sendRedirect("http://www.baidu.com");
	}
}

MD5加密utils:

public class MD5Util {

    // 加盐
    private static final String SALT = "xwhy";

    public static String encode(String password) {
        password = password + SALT;
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        char[] charArray = password.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++)
            byteArray[i] = (byte) charArray[i];
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }

            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

    public static void main(String[] args) {
        System.out.println(MD5Util.encode("123456"));
    }
}

MyUserDetailService: 设置用户动态信息,认证用户,给用户分配权限

@Service
public class MyUserDetailService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.findByUsername(username);
        if(!Objects.isNull(user)){
            List<Permission> permissionList = userMapper.findPermissionByUsername(username);
            if(permissionList!=null&&permissionList.size()>0){
                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                permissionList.stream().forEach(e->{
                    authorities.add(new SimpleGrantedAuthority(e.getPermTag()));
                });
                user.setAuthorities(authorities);
            }
        }
        return user;
    }
}

启动类:

@SpringBootApplication
@MapperScan("com.xwhy.mapper")
public class AppSecurity {
    public static void main(String[] args) {
        SpringApplication.run(AppSecurity.class,args);
    }
}

配置文件:application.yml

# 配置freemarker
spring:
  freemarker:
    # 设置模板后缀名
    suffix: .ftl
    # 设置文档类型
    content-type: text/html
    # 设置页面编码格式
    charset: UTF-8
    # 设置页面缓存
    cache: false
    # 设置ftl文件路径
    template-loader-path:
      - classpath:/templates
  # 设置静态文件路径,js,css等
  mvc:
    static-path-pattern: /static/**
  ####整合数据库层
  datasource:
    name: test
    url: jdbc:mysql://127.0.0.1:3306/rbac_db
    username: root
    password: 123456
    # druid 连接池
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver

相关页面:

springcloud实现密码找回 springcloudsecurity_Springcloud

login.ftl:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>

	<h1>每特教育--权限控制登陆系统</h1>
	<form action="/login" method="post">
		<span>用户名称</span><input type="text" name="username" /> <br>
		<span>用户密码</span><input type="password" name="password" /> <br>
		<input type="submit" value="登陆"> 

	</form>
	
<#if RequestParameters['error']??>
用户名称或者密码错误
</#if>

</body>
</html>