SpringBoot整合SpringSecurity(一)

SpringBoot整合SpringSecurity

源碼地址:https://gitee.com/lin8081/LWH10

1.基本介紹

SpringSecurity概念

  1. SpringSecurity是一個安全管理框架,提供了認證與授權這些基本操作
  2. 認證: 用戶訪問系統,系統校驗用戶身份是否合法的過程就是認證。常見的認證: 登陸認證。
  3. 授權:用戶認證後,訪問系統資源,校驗用戶是否有權限訪問系統資源的過程就是授權訪問校驗,簡稱爲授權。權限校驗過程:1.獲取用戶的權限; 2. 知道訪問資源需要的權限;3.拿着訪問資源需要的權限去用戶權限列表查找,找到則授權訪問。否則拒絕訪問。

2.創建數據庫表

一般權限控制有三層,即:用戶<–>角色<–>權限,用戶與角色是多對多,角色和權限也是多對多。這裏我們先暫時不考慮權限,只考慮用戶<–>角色。 這裏爲了測試,表結構簡單設計,後續可以根據業務添加先關字段。

數據庫:Mysql 5.6 創建表結構:

-- 用戶表
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

初始化數據:

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片段:

<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);
    }
}

加密後密文:$2a1010z/GksRmh.CxJbP3nAiFWUu0yLfJ6YvUL04OxttZXBnClvEbU3KQgy

注:隨機鹽加密所以兩次加密後結果不一樣

2.啓動工程:

ROLE_ADMIN 賬戶:用戶名: admin,密碼: 123

ROLE_USER 賬戶:用戶名: user,密碼: 123

3.網頁顯示
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章