springboot-28-security(一)用户角色控制
spring security 使用众多的拦截器实现权限控制的, 其核心有2个重要的概念: 认证(Authentication) 和授权 (Authorization)), 认证就是确认用户可以访问当前系统, 授权即确定用户有相应的权限,
现在先大概过一遍整个流程,用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。
访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面 ( http://blog.csdn.net/u012367513/article/details/38866465)
本例的用户和角色信息存储在mysql, 使用mybatis进行查询: http://www.cnblogs.com/wenbronk/p/7357996.html
项目 使用 idea + gradle
dependencies { compile("org.springframework.boot:spring-boot-devtools") compile("org.springframework.boot:spring-boot-starter") compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-log4j2") compile("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml") compile("org.codehaus.groovy:groovy-all:2.4.12") compile("org.springframework.boot:spring-boot-starter-security") compile("org.springframework.boot:spring-boot-starter-thymeleaf") compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity4") compile ("mysql:mysql-connector-java") compile 'com.alibaba:druid-spring-boot-starter:1.1.2' compile ("org.mybatis.spring.boot:mybatis-spring-boot-starter:1.1.1") compile 'com.alibaba:fastjson:1.1.15' compile 'javax.inject:javax.inject:1' testCompile group: 'junit', name: 'junit', version: '4.12' testCompile("org.springframework.boot:spring-boot-starter-test") }
1, 导入基础数据, 调试mybatis
1) , 建表
/* Navicat MySQL Data Transfer Source Server : 本地 Source Host : localhost:3306 Source Database : test Target Server Type : MYSQL Date: 2017-8-14 22:17:33 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `sys_user` -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` INT (32) NOT NULL AUTO_INCREMENT COMMENT '主键id', `username` varchar(32) DEFAULT NULL COMMENT '用户名', `password` varchar(32) DEFAULT NULL COMMENT '密码', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for `sys_role` -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` INT (32) NOT NULL AUTO_INCREMENT COMMENT '主键id', `name` varchar(32) DEFAULT NULL COMMENT '用户名', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for `sys_role_user` -- ---------------------------- DROP TABLE IF EXISTS `sys_role_user`; CREATE TABLE `sys_role_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', `sys_user_id` INT(32) NOT NULL COMMENT 'user_id', `sys_role_id` INT(32) NOT NULL COMMENT 'role_id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8; ALTER TABLE sys_role_user ADD CONSTRAINT sys_FK1 FOREIGN KEY(sys_user_id) REFERENCES sys_user(id); ALTER TABLE sys_role_user ADD CONSTRAINT role_FK2 FOREIGN KEY(sys_role_id) REFERENCES sys_role(id);
导入数据
insert into SYS_USER (id,username, password) values (1,'vini', '123'); insert into SYS_USER (id,username, password) values (2,'bronk', '123'); insert into SYS_ROLE(id,name) values(1,'ROLE_ADMIN'); insert into SYS_ROLE(id,name) values(2,'ROLE_USER'); insert into SYS_ROLE_USER(SYS_USER_ID,sys_role_id) values(1,1); insert into SYS_ROLE_USER(SYS_USER_ID,sys_role_id) values(2,2);
在 application.yml中配置如下, 可以在启动程序时自动执行
spring: datasource: url: jdbc:mysql://localhost:3306/springboot username: root password: root # type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver
# 一下2行 schema: classpath:security.sql data: classpath:security-data.sql
2), mapper映射
package com.wenbronk.security.mapper import com.wenbronk.security.entity.SysUser /** * Created by wenbronk on 2017/8/14. */ interface SysUserMapper { SysUser findByUserName(String username) }
在 resources/mybatis/mapper中添加:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.wenbronk.security.mapper.SysUserMapper"> <resultMap id="sys_user_map" type="SysUser"> <id property="id" column="id" /> <result property="username" column="username" /> <result property="password" column="password" /> <collection property="roles" ofType="SysRole"> <result column="name" property="name" /> </collection> </resultMap> <select id="findByUserName" parameterType="string" resultMap="sys_user_map"> select u.id, u.username, u.password, r.name from sys_user u LEFT JOIN sys_role_user s on u.id = s.sys_user_id LEFT JOIN sys_role r on r.id = s.sys_role_id WHERE username = #{username} </select> </mapper>
在application.yml中配置
mybatis: config-location: classpath:mybatis/SqlMapConfig.xml mapper-locations: classpath:mybatis/mapper/*.xml
在main方法上添加mapper扫描:
@SpringBootApplication @MapperScan("com.wenbronk.security.mapper") public class SecurityApplication { public static void main(String[] args) { SpringApplication.run(SecurityApplication.class); } }
3), 实体类
class Msg { String title String content String etraInfo Msg() { } Msg(String title, String content, String etraInfo) { this.title = title this.content = content this.etraInfo = etraInfo } }
SysUser.groovy
package com.wenbronk.security.entity /** * Created by wenbronk on 2017/8/14. */ class SysUser { int id def username def password List<SysRole> roles @Override public String toString() { return "SysUser{" + "id=" + id + ", username=" + username + ", password=" + password + ", roles=" + roles + '}'; } }
SysRole.groovy
package com.wenbronk.security.entity /** * Created by wenbronk on 2017/8/14. */ class SysUser { int id def username def password List<SysRole> roles @Override public String toString() { return "SysUser{" + "id=" + id + ", username=" + username + ", password=" + password + ", roles=" + roles + '}'; } }
4) , 测试mybatis
@Test void test2() { def name = sysUserMapper.findByUserName('vini') println name }
2, security相关配置
1), CustomerUserService.groovy
package com.wenbronk.security.security.service import com.wenbronk.security.entity.SysRole import com.wenbronk.security.entity.SysUser import com.wenbronk.security.mapper.SysUserMapper import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.userdetails.User 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.Service import javax.inject.Inject /** * Created by wenbronk on 2017/8/15. */ @Service class CustomUserService implements UserDetailsService { @Inject SysUserMapper sysUserMapper; @Override UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { def sysUser = sysUserMapper.findByUserName(s) as SysUser assert sysUser != null List<SimpleGrantedAuthority> authorities = new ArrayList<>() for(SysRole role : sysUser.getRoles()) { authorities.add(new SimpleGrantedAuthority(role.getName())) println role.getName(); } return new User(sysUser.getUsername(), sysUser.getPassword(), authorities) } }
2), WebSecurityConfig.groovy
package com.wenbronk.security.security.config import com.wenbronk.security.security.service.CustomUserService 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 javax.inject.Inject /** * Created by wenbronk on 2017/8/15. */ @Configuration @EnableWebSecurity class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Inject CustomUserService customUserService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserService); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() // 任何请求都拦截 .and() .formLogin() .loginPage("/login") .failureUrl("/login?error") .permitAll() // 登陆后可访问任意页面 .and() .logout().permitAll(); // 注销后任意访问 } }
3, 页面
1), 页面转向设置
package com.wenbronk.security.config import org.springframework.context.annotation.Configuration import org.springframework.web.servlet.config.annotation.ViewControllerRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter /** * Created by wenbronk on 2017/8/15. */ @Configuration class WebMvcConfig extends WebMvcConfigurerAdapter{ @Override void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login") } }
controller.groovy
package com.wenbronk.security.controller import com.wenbronk.security.entity.Msg import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.RequestMapping /** * Created by wenbronk on 2017/8/14. */ @Controller class SecurityController { @RequestMapping("/") def index(Model model) { def msg = new Msg("测试标题", "测试内容", "额外信息, 只对管理员显示") model.addAttribute("msg", msg); "home" } }
在resources下放入静态资源, bootstramp.min.css, 以及thymeleaf页面
login.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta content="text/html;charset=UTF-8"/> <title>登录页面</title> <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/> <style type="text/css"> body { padding-top: 50px; } .starter-template { padding: 40px 15px; text-align: center; } </style> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">Spring Security演示</a> </div> <div id="navbar" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li><a th:href="@{/}"> 首页 </a></li> </ul> </div><!--/.nav-collapse --> </div> </nav> <div class="container"> <div class="starter-template"> <p th:if="${param.logout}" class="bg-warning">已成功注销</p><!-- 1 --> <p th:if="${param.error}" class="bg-danger">有错误,请重试</p> <!-- 2 --> <h2>使用账号密码登录</h2> <form name="form" th:action="@{/login}" action="/login" method="POST"> <!-- 3 --> <div class="form-group"> <label for="username">账号</label> <input type="text" class="form-control" name="username" value="" placeholder="账号" /> </div> <div class="form-group"> <label for="password">密码</label> <input type="password" class="form-control" name="password" placeholder="密码" /> </div> <input type="submit" id="login" value="Login" class="btn btn-primary" /> </form> </div> </div> </body> </html>
home.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> <head> <meta content="text/html;charset=UTF-8"/> <title sec:authentication="name"></title> <link rel="stylesheet" th:href="@{css/bootstrap.min.css}" /> <style type="text/css"> body { padding-top: 50px; } .starter-template { padding: 40px 15px; text-align: center; } </style> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">Spring Security演示</a> </div> <div id="navbar" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li><a th:href="@{/}"> 首页 </a></li> </ul> </div><!--/.nav-collapse --> </div> </nav> <div class="container"> <div class="starter-template"> <h1 th:text="${msg.title}"></h1> <p class="bg-primary" th:text="${msg.content}"></p> <div sec:authorize="hasRole('ROLE_ADMIN')"> <!-- 用户类型为ROLE_ADMIN 显示 --> <p class="bg-info" th:text="${msg.etraInfo}"></p> </div> <div sec:authorize="hasRole('ROLE_USER')"> <!-- 用户类型为 ROLE_USER 显示 --> <p class="bg-info">无更多信息显示</p> </div> <form th:action="@{/logout}" method="post"> <input type="submit" class="btn btn-primary" value="注销"/> </form> </div> </div> </body> </html>
参见: JavaEE颠覆者, springboot实战