本章源码地址:https://gitee.com/lin8081/LWH10
下一章案例:Springboot整合SpringSecurity二css
SpringSecurity概念html
通常权限控制有三层,即:用户
<–>角色
<–>权限
,用户与角色是多对多,角色和权限也是多对多。这里咱们先暂时不考虑权限,只考虑用户
<–>角色
。 这里为了测试,表结构简单设计,后续能够根据业务添加先关字段。java
数据库:Mysql 5.6 建立表结构:mysql
-- 用户表 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
初始化数据:git
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规定的,不要乱起名字。web
用于登陆的 login.html
片断以及用户登陆成功后跳转的 index.html
,将其放置在工程 resources/static
目录下:spring
login.html片断:sql
<form method="post" action="/login"> <!-- 帐号 --> <div class="layui-form-item"> <label class="layui-form-label">帐号</label> <div class="layui-input-block"> <input name="username" id="userName" value="admin" placeholder="默认帐号:admin" type="text" lay-verify="required" class="layui-input"> </div> </div> <!-- 密码 --> <div class="layui-form-item"> <label class="layui-form-label">密码</label> <div class="layui-input-block"> <input name="password" id="password" value="123" placeholder="默认密码:111111" type="password" lay-verify="required" class="layui-input"> </div> </div> <div> <button type="submit" class="layui-btn layui-btn-fluid" >登 录</button> </div> </form>
首页index.html数据库
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录成功</h1> <a href="/admin">检测ROLE_ADMIN角色</a> <a href="/user">检测ROLE_USER角色</a> <button onclick="window.location.href='/logout'">退出登陆</button> </body> </html>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.16</version> </dependency> </dependencies>
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
实体类:跨域
SysUser:
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:
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:
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:
@Mapper public interface SysUserMapper { SysUser selectById(Integer id); SysUser selectByName(String username); }
SysRoleMapper:
@Mapper public interface SysRoleMapper { SysRole selectById(Integer id); }
SysUserRoleMapper:
@Mapper public interface SysUserRoleMapper { List<SysUserRole> listByUserId(Integer userId); }
Service:
SysUserService:
接口
public interface SysUserService { SysUser selectById(Integer id); SysUser selectByName(String name); }
实现类
@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:
接口
public interface SysRoleService { public SysRole selectById(Integer id); }
实现类
@Service public class SysRoleServiceImpl implements SysRoleService { @Autowired private SysRoleMapper roleMapper; @Override public SysRole selectById(Integer id) { return roleMapper.selectById(id); } }
SysUserRoleService:
接口
public interface SysUserRoleService { List<SysUserRole> listByUserId(Integer userId); }
实现类
@Service public class SysUserRoleServiceImpl implements SysUserRoleService { @Autowired private SysUserRoleMapper userRoleMapper; @Override public List<SysUserRole> listByUserId(Integer userId) { return userRoleMapper.listByUserId(userId); } }
Controller:
@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
用于判断用户是否有指定权限,没有就不能访问UserMapper.xml
<?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.lwh.dao.SysUserMapper" > <resultMap id="BaseResultMap" type="com.lwh.model.user.SysUser" > <id column="id" property="id" jdbcType="INTEGER"/> <result column="username" property="username" jdbcType="VARCHAR"/> <result column="password" property="password" jdbcType="VARCHAR"/> <result column="true_name" property="trueName" jdbcType="VARCHAR"/> <result column="remarks" property="remarks" jdbcType="VARCHAR"/> <result column="phone" property="phone" jdbcType="VARCHAR"/> </resultMap> <sql id="Base_Column_List" > id,username,password,true_name,remarks,phone </sql> <select id="selectById" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM sys_user WHERE id = #{id} </select> <select id="selectByName" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM sys_user WHERE username = #{username} </select> </mapper>
RoleMapper.xml
<?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.lwh.dao.SysRoleMapper" > <resultMap id="BaseResultMap" type="com.lwh.model.user.SysRole" > <id column="id" property="id" jdbcType="INTEGER"/> <result column="name" property="name" jdbcType="VARCHAR"/> </resultMap> <sql id="Base_Column_List" > id,name </sql> <select id="selectById" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM sys_role WHERE id = #{id} </select> </mapper>
UserRoleMapper.xml
<?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.lwh.dao.SysUserRoleMapper" > <resultMap id="BaseResultMap" type="com.lwh.model.user.SysUserRole" > <id column="user_id" property="userId" jdbcType="INTEGER"/> <id column="role_id" property="roleId" jdbcType="INTEGER"/> </resultMap> <sql id="Base_Column_List" > user_id,role_id </sql> <select id="listByUserId" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM sys_user_role WHERE user_id = #{userId} </select> </mapper>
自定义 UserDetailService
, 将用户信息和权限注入进来。
CustomUserDetailsService:
@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<GrantedAuthority> authorities = new ArrayList<>(); // 从数据库中取出用户信息 SysUser user = userService.selectByName(username); // 判断用户是否存在 if(user == null) { throw new UsernameNotFoundException("用户名不存在"); } // 添加权限 List<SysUserRole> 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); } }
Spring Security默认是禁用注解的,要想开启注解, 须要在继承WebSecurityConfigurerAdapter
的类上加@EnableGlobalMethodSecurity
注解, 来判断用户对某个控制层的方法是否具备访问权限
例如:上面代码方法前不加@preAuthorize注解,意味着全部用户都能访问方法,若是加上注解,表示只要具有指定角色的用户才有权限访问。也就是得继承WebSecurityConfigurerAdapter
类并加上@EnableGlobalMethodSecurity
注解才能在控制层加@preAuthorize注解
@EnableGlobalMethodSecurity详解:
@EnableGlobalMethodSecurity(securedEnabled=true)
开启@Secured
注解过滤权限@EnableGlobalMethodSecurity(jsr250Enabled=true)
开启@RolesAllowed
注解过滤权限@EnableGlobalMethodSecurity(prePostEnabled=true)
使用表达式实现方法级别的安全性 ,4个注解可用:
@PreAuthorize
在方法调用以前,基于表达式的计算结果来限制对方法的访问@PostAuthorize
容许方法调用,可是若是表达式计算结果为false,将抛出一个安全性异常@PostFilter
容许方法调用,但必须按照表达式来过滤方法的结果@PreFilter
容许方法调用,但必须在进入方法以前过滤输入值首先,咱们将自定义的 userDetailsService
注入进来,在 configure()
方法中使用 auth.userDetailsService()
方法替换掉默认的 userDetailsService
。
这里咱们还指定了密码的加密模式(5.0版本强制要求设置),咱们采用SpringSecurity提供的加密模式:BCryptPasswordEncoder
,它帮咱们实现了PasswordEncoder
,固然也能够自定义加密模式。
@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"); } }
1.启动工程以前,因为数据库用户表的密码初始化的是明文,这里咱们须要使用SpringSecurity 提供的加密工具类对密码进行从新加密修改。
加密类:
public class SpringSecurityUtil { public static void main(String[] args) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String password = bCryptPasswordEncoder.encode("123"); System.out.println(password); } }
加密后密文:$2a z/GksRmh.CxJbP3nAiFWUu0yLfJ6YvUL04OxttZXBnClvEbU3KQgy
注:随机盐加密因此两次加密后结果不同
2.启动工程:
ROLE_ADMIN 帐户:用户名: admin,密码: 123
ROLE_USER 帐户:用户名: user,密码: 123
3.网页显示