SpringBoot整合SpringSecurity(一)

SpringBoot整合SpringSecurity

本章源码地址:https://gitee.com/lin8081/LWH10
下一章案例:Springboot整合SpringSecurity二css

1.基本介绍

SpringSecurity概念html

  1. SpringSecurity是一个安全管理框架,提供了认证与受权这些基本操做
  2. 认证: 用户访问系统,系统校验用户身份是否合法的过程就是认证。常见的认证: 登录认证。
  3. 受权:用户认证后,访问系统资源,校验用户是否有权限访问系统资源的过程就是受权访问校验,简称为受权。权限校验过程:1.获取用户的权限; 2. 知道访问资源须要的权限;3.拿着访问资源须要的权限去用户权限列表查找,找到则受权访问。不然拒绝访问。

2.建立数据库表

通常权限控制有三层,即:用户<–>角色<–>权限,用户与角色是多对多,角色和权限也是多对多。这里咱们先暂时不考虑权限,只考虑用户<–>角色。 这里为了测试,表结构简单设计,后续能够根据业务添加先关字段。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

3.准备页面

用于登陆的 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>

4.导入依赖

<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>

5.配置application.yml

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:

    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 用于判断用户是否有指定权限,没有就不能访问

7.配置mapper.xml

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>

8.配置SpringSecurity

1.UserDetailService

自定义 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);
    }

}

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
@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 提供的加密工具类对密码进行从新加密修改。

加密类:

public class SpringSecurityUtil {
    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String password = bCryptPasswordEncoder.encode("123");

        System.out.println(password);
    }
}

加密后密文:$2a 10 10 z/GksRmh.CxJbP3nAiFWUu0yLfJ6YvUL04OxttZXBnClvEbU3KQgy

注:随机盐加密因此两次加密后结果不同

2.启动工程:

ROLE_ADMIN 帐户:用户名: admin,密码: 123

ROLE_USER 帐户:用户名: user,密码: 123

3.网页显示
在这里插入图片描述
在这里插入图片描述