# LWH10 **Repository Path**: lin8081/LWH10 ## Basic Information - **Project Name**: LWH10 - **Description**: SpringBoot整合SpringSecurity之入门案例 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-03-09 - **Last Updated**: 2020-12-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## SpringBoot整合SpringSecurity ### 1.基本介绍 **SpringSecurity概念** 1. SpringSecurity是一个安全管理框架,提供了认证与授权这些基本操作 2. 认证: 用户访问系统,系统校验用户身份是否合法的过程就是认证。常见的认证: 登陆认证。 3. 授权:用户认证后,访问系统资源,校验用户是否有权限访问系统资源的过程就是授权访问校验,简称为授权。权限校验过程:1.获取用户的权限; 2. 知道访问资源需要的权限;3.拿着访问资源需要的权限去用户权限列表查找,找到则授权访问。否则拒绝访问。 ### 2.创建数据库表 一般权限控制有三层,即:`用户`<–>`角色`<–>`权限`,用户与角色是多对多,角色和权限也是多对多。这里我们先暂时不考虑权限,只考虑`用户`<–>`角色`。 这里为了测试,表结构简单设计,后续可以根据业务添加先关字段。 数据库:Mysql 5.6 创建表结构: ```sql -- 用户表 CREATE TABLE `sys_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL COMMENT '账号', `password` varchar(255) NOT NULL COMMENT '密码', `true_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `phone` varchar(50) DEFAULT NULL COMMENT '手机', `email` varchar(25) DEFAULT NULL COMMENT '邮箱', `createtime` varchar(25) DEFAULT NULL COMMENT '创建时间', `status` int(2) DEFAULT NULL COMMENT '状态', `remarks` varchar(1000) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 -- 角色表 CREATE TABLE `sys_role` ( `id` int(11) NOT NULL, `name` varchar(50) DEFAULT NULL COMMENT '角色名', `roleDesc` varchar(50) DEFAULT NULL COMMENT '角色说明', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 -- 用户-角色关系表 CREATE TABLE `sys_user_role` ( `user_id` int(11) NOT NULL, `role_id` int(11) NOT NULL, PRIMARY KEY (`user_id`,`role_id`), KEY `fk_role_id` (`role_id`), CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ``` 初始化数据: ```java insert into `sys_role` values ('1', 'ROLE_ADMIN','管理员'); insert into `sys_role` values ('2', 'ROLE_USER','普通用户'); insert into `sys_user` (id, username,password) values ('1', 'admin', '123'); insert into `sys_user` (id, username,password) values ('2', 'user', '123'); INSERT INTO `sys_user_role` VALUES ('1', '1'); INSERT INTO `sys_user_role` VALUES ('2', '2'); ``` **注意:**这里的权限格式为`ROLE_XXX`,是Spring Security规定的,不要乱起名字。 ### 3.准备页面 用于登录的 `login.html` 片段以及用户登录成功后跳转的 `index.html`,将其放置在工程 `resources/static` 目录下: login.html片段: ```html
``` 首页index.html ```html Title

登陆成功

检测ROLE_ADMIN角色 检测ROLE_USER角色 ``` ### 4.导入依赖 ```xml org.springframework.boot spring-boot-starter-parent 2.0.5.RELEASE org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.1 mysql mysql-connector-java 5.1.34 com.alibaba druid-spring-boot-starter 1.1.16 org.projectlombok lombok 1.16.16 ``` ### 5.配置application.yml ```xml server: port: 9000 spring: application: name: auth01 datasource: druid: url: jdbc:mysql://localhost:3306/auth01?useUnicode=true&useJDBCCompliantTimezoneShift=true&serverTimezone=UTC&characterEncoding=utf8 username: root password: root driverClassName: com.mysql.jdbc.Driver initialSize: 5 #初始建立连接数量 minIdle: 5 #最小连接数量 maxActive: 20 #最大连接数量 maxWait: 10000 #获取连接最大等待时间,毫秒 testOnBorrow: true #申请连接时检测连接是否有效 testOnReturn: false #归还连接时检测连接是否有效 timeBetweenEvictionRunsMillis: 60000 #配置间隔检测连接是否有效的时间(单位是毫秒) minEvictableIdleTimeMillis: 300000 #连接在连接池的最小生存时间(毫秒) # mybatis mybatis: #config-location: classpath:mybatis/mybatis-config.xml # mybatis配置文件位置 mapper-locations: - classpath*:mapper/**/*.xml # mapper映射文件位置 type-aliases-package: com.lwh.model # 别名包 configuration: cache-enabled: true ``` ### 6.创建实体类、DAO、Service和Controller 实体类: - SysUser: ```java public class SysUser implements Serializable{ private static final long serialVersionUID = -2836223054703407171L; private Integer id; private String username; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } ``` - SysRole: ```java public class SysRole implements Serializable { private static final long serialVersionUID = 7510551869226022669L; private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` - SysUserRole: ```java public class SysUserRole implements Serializable{ private static final long serialVersionUID = -3256750757278740295L; private Integer userId; private Integer roleId; public SysUserRole() { } public SysUserRole(Integer userId, Integer roleId) { this.userId = userId; this.roleId = roleId; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } } ``` DAO: - SysUserMapper: ```java @Mapper public interface SysUserMapper { SysUser selectById(Integer id); SysUser selectByName(String username); } ``` - SysRoleMapper: ```java @Mapper public interface SysRoleMapper { SysRole selectById(Integer id); } ``` - SysUserRoleMapper: ```java @Mapper public interface SysUserRoleMapper { List listByUserId(Integer userId); } ``` Service: - SysUserService: 接口 ```java public interface SysUserService { SysUser selectById(Integer id); SysUser selectByName(String name); } ``` 实现类 ```java @Service public class SysUserServiceImpl implements SysUserService { @Autowired private SysUserMapper userMapper; @Override public SysUser selectById(Integer id) { return userMapper.selectById(id); } @Override public SysUser selectByName(String name) { return userMapper.selectByName(name); } } ``` - SysRoleService: 接口 ```java public interface SysRoleService { public SysRole selectById(Integer id); } ``` 实现类 ```java @Service public class SysRoleServiceImpl implements SysRoleService { @Autowired private SysRoleMapper roleMapper; @Override public SysRole selectById(Integer id) { return roleMapper.selectById(id); } } ``` - SysUserRoleService: 接口 ```java public interface SysUserRoleService { List listByUserId(Integer userId); } ``` 实现类 ```java @Service public class SysUserRoleServiceImpl implements SysUserRoleService { @Autowired private SysUserRoleMapper userRoleMapper; @Override public List listByUserId(Integer userId) { return userRoleMapper.listByUserId(userId); } } ``` Controller: ```java @Controller public class LoginController { private Logger logger = LoggerFactory.getLogger(LoginController.class); @GetMapping("/login") public String showLogin() { return "/pages/login.html"; } @GetMapping("/") public String showHome() { String username = SecurityContextHolder.getContext().getAuthentication().getName(); logger.info("当前登陆用户:" + username); return "/pages/index.html"; } @GetMapping("/admin") @ResponseBody @PreAuthorize("hasRole('ROLE_ADMIN')") public String printAdmin() { return "如果你看见这句话,说明你有ROLE_ADMIN角色"; } @GetMapping("/user") @ResponseBody @PreAuthorize("hasRole('ROLE_USER')") public String printUser() { return "如果你看见这句话,说明你有ROLE_USER角色"; } } ``` **注意**:如代码所示: - 获取当前登录用户:`SecurityContextHolder.getContext().getAuthentication()` - `@PreAuthorize` 用于判断用户是否有指定权限,没有就不能访问 ### 7.配置mapper.xml UserMapper.xml ```xml id,username,password,true_name,remarks,phone ``` RoleMapper.xml ```xml id,name ``` UserRoleMapper.xml ```xml user_id,role_id ``` ### 8.配置SpringSecurity #### 1.UserDetailService 自定义 `UserDetailService`, 将用户信息和权限注入进来。 CustomUserDetailsService: ```java @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private SysUserService userService; @Autowired private SysRoleService roleService; @Autowired private SysUserRoleService userRoleService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Collection authorities = new ArrayList<>(); // 从数据库中取出用户信息 SysUser user = userService.selectByName(username); // 判断用户是否存在 if(user == null) { throw new UsernameNotFoundException("用户名不存在"); } // 添加权限 List userRoles = userRoleService.listByUserId(user.getId()); for (SysUserRole userRole : userRoles) { SysRole role = roleService.selectById(userRole.getRoleId()); authorities.add(new SimpleGrantedAuthority(role.getName())); } // 返回UserDetails实现类 return new User(user.getUsername(), user.getPassword(), authorities); } } ``` #### 2.WebSecurityConfig Spring Security默认是禁用注解的,要想开启注解, 需要在继承`WebSecurityConfigurerAdapter`的类上加`@EnableGlobalMethodSecurity`注解, 来判断用户对某个控制层的方法是否具有访问权限 例如:上面代码方法前不加@preAuthorize注解,意味着所有用户都能访问方法,如果加上注解,表示只要具备指定角色的用户才有权限访问。也就是得继承`WebSecurityConfigurerAdapter`类并加上`@EnableGlobalMethodSecurity`注解才能在控制层加@preAuthorize注解 **@EnableGlobalMethodSecurity详解**: - `@EnableGlobalMethodSecurity(securedEnabled=true)` 开启`@Secured` 注解过滤权限 - `@EnableGlobalMethodSecurity(jsr250Enabled=true)`开启`@RolesAllowed `注解过滤权限 - `@EnableGlobalMethodSecurity(prePostEnabled=true)`使用表达式实现方法级别的安全性 ,4个注解可用: 1. `@PreAuthorize` 在方法调用之前,基于表达式的计算结果来限制对方法的访问 2. `@PostAuthorize` 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常 3. `@PostFilter` 允许方法调用,但必须按照表达式来过滤方法的结果 4. `@PreFilter `允许方法调用,但必须在进入方法之前过滤输入值 首先,我们将自定义的 `userDetailsService` 注入进来,在 `configure()` 方法中使用 `auth.userDetailsService()` 方法替换掉默认的 `userDetailsService `。 这里我们还指定了密码的加密模式(5.0版本强制要求设置),我们采用SpringSecurity提供的加密模式:`BCryptPasswordEncoder`,它帮我们实现了`PasswordEncoder`,当然也可以自定义加密模式。 - WebSecurityConfig.java ```java @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 如果有允许匿名的url,填在下面 // .antMatchers().permitAll() .anyRequest().authenticated() .and() // 设置登陆页 .formLogin().loginPage("/login") // 设置登陆成功页 .defaultSuccessUrl("/").permitAll() // 自定义登陆用户名和密码参数,默认为username和password // .usernameParameter("username") // .passwordParameter("password") .and() .logout().permitAll(); // 关闭CSRF跨域 http.csrf().disable(); } @Override public void configure(WebSecurity web) throws Exception { // 设置拦截忽略文件夹,可以对静态资源放行 web.ignoring().antMatchers( "/**/**.gif", "/**/**.jpg", "/**/**.css", "/**/**.jq", "/**/**.js", "/**/**.ttf", "/**/**.woff", "/**/**.woff2", "/**/**.png"); } } ``` ### 9.运行测试 1.启动工程之前,由于数据库用户表的密码初始化的是明文,这里我们需要使用SpringSecurity 提供的加密工具类对密码进行重新加密修改。 加密类: ```java public class SpringSecurityUtil { public static void main(String[] args) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String password = bCryptPasswordEncoder.encode("123"); System.out.println(password); } } ``` 加密后密文:$2a$10$z/GksRmh.CxJbP3nAiFWUu0yLfJ6YvUL04OxttZXBnClvEbU3KQgy 注:随机盐加密所以两次加密后结果不一样 2.启动工程: ROLE_ADMIN 账户:用户名: admin,密码: 123 ROLE_USER 账户:用户名: user,密码: 123