1. 程式人生 > >SpringBoot 2.0 | Security+Mybatis 許可權認證

SpringBoot 2.0 | Security+Mybatis 許可權認證

1.簡介

Spring Security 是 Spring 家族的一個安全框架, 提供了全面的安全解決方案 , 對使用者的身份進行認證, 以及驗證每一個使用者所具有的的許可權, 根據使用者的許可權限制使用者的操作。

Mybatis 是一款優秀的持久層框架 , 支援自定義 SQL 以及各種高階對映 , 與 JPA 的自動生成 SQL 相比, 它更加靈活, 本例使用 Mybatis 儲存使用者的身份和許可權, 通過 Security 獲取使用者資訊, 對使用者的許可權和操作進行管理。

2.實現程式碼

1.專案配置

spring:
  datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: roof
      url: jdbc:mysql://localhost:3306/security?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true

    # 配置前端Thymeleaf模板引擎
  thymeleaf:
  # 打包末尾無/
    prefix: classpath:/templates/
    check-template-location: true
    suffix: .html
    encoding: UTF-8
    servlet:
      content-type: text/html
    mode: HTML5
    # 禁止後實現前端熱部署
    cache: false

# 整合Mybatis
mybatis:
  # Mybatis對映
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.hly.springBootSecurityMybatis.entity

# 埠設定
server:
  port: 8081

2.Security 配置

@Configuration
@EnableWebSecurity//開啟WebSecurity功能
@EnableGlobalMethodSecurity(prePostEnabled = true)//開啟方法上的保護
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Bean
   UserDetailsService userService(){
       return  new UserService();
   }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

        //從資料庫中獲取使用者認證資訊
        auth.userDetailsService(userService());
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //不需要驗證的資源
                .antMatchers("/css/**", "/index").permitAll()
                //需要驗證,角色為Role
                .antMatchers("/article/**").hasAnyRole("ADMIN","STUDENT","TEACHER")
                .antMatchers("/admin/**").hasAnyRole("ADMIN","STUDENT","TEACHER")
                .and()
                //表單的登入地址和失敗地址
                .formLogin().loginPage("/login").failureForwardUrl("/loginError")
                .and()
                //異常處理介面
                .exceptionHandling().accessDeniedPage("/401");
        http.logout().logoutSuccessUrl("/");
    }
}

3.controller 層

@Controller
public class ArticleController {

    @Autowired
    ArticleService articleService;
    /**
     * 檢視文章列表
     * @param model
     * @return
     */
    @RequestMapping("/article")
    public ModelAndView articleList(Model model){
        List<Article> list = articleService.getArticles();
        model.addAttribute("articlesList",list);
        return new ModelAndView("article/list","articleModel",model);
    }
    /**
     * 給方法設定許可權,沒有ADMIN許可權的使用者不能刪除文章
     * @param id
     * @param model
     * @return
     */
    @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
    @GetMapping(value = "/article/{id}/deletion")
    public ModelAndView delete(@PathVariable("id")int id,Model model){
        articleService.deleteArticle(id);
        model.addAttribute("articlesList",articleService.getArticles());
        return new ModelAndView("article/list","articleModel",model);
    }
}
@Controller
public class LoginController {

    @RequestMapping("/")
    public String root(){
        return "redirect:/index";
    }

    @RequestMapping("index")
    public String index(){
        return "index";
    }

    //@RequestMapping將接收Get,Post,Head,Options等所有的請求方式
    @RequestMapping(value = "/login")
    public String login(){
        return "login";
    }

    @RequestMapping("/loginError")
    public String loginError(ModelAndView modelAndView){
        modelAndView.addObject("loginError",true);
        return "login";
    }

    @RequestMapping("/admin")
    public String admin(){
        return "admin/admin";
    }

    //@RequestMapping(method = RequestMethod.GET)的縮寫
    @GetMapping("401")
    public String error(){
        return "401";
    }
    
    @GetMapping("/logout")
    public String logout(){
        return "/";
    }
}

4.dao 層

@Repository
public interface UserDao {

    //通過使用者名稱查詢使用者
    public User findUserByUsername(String username);
}

5. entity

Article

public class Article {
    private int id;
    private String title;
    private String content;
    
    public Article() {
    }

    public Article(int id, String title, String content) {
        this.id = id;
        this.title = title;
        this.content = content;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
    @Override
    public String toString() {
        return "Article{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}

Role

public class Role implements GrantedAuthority {

    private int id;
    private String name;

    @Override
    public String getAuthority() {
        return name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
    
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

User

public class User implements UserDetails, Serializable {

    private int id;
    private String username;
    private String password;
    private List<Role> roles;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

6.service層

@Service
public class ArticleServiceImpl implements ArticleService {

    private List<Article> list = new ArrayList<>();

    public ArticleServiceImpl() {
        list.add(new Article(1,"java","java從入門到搬磚"));
        list.add(new Article(2,"SQL","SQL從刪庫到跑路"));
       
    }
    @Override
    public List<Article> getArticles() {
        return list;
    }
    @Override
    public void deleteArticle(int id) {
        Iterator iter = list.iterator();
        while(iter.hasNext()){
            Article article = (Article)iter.next();
            if(article.getId()==id){
                iter.remove();
            }
        }
    }
}
public interface ArticleService {
    List<Article> getArticles();
    void deleteArticle(int id);
}

@Service
public class UserService implements UserDetailsService {
    @Autowired
    private UserDao userDao;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.err.println(userDao.findUserByUsername(username));
        return userDao.findUserByUsername(username);
    }
}

7.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.hly.springBootSecurityMybatis.dao.UserDao">

    <resultMap id="userMap" type="User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <collection property="roles" ofType="Role">
            <result property="name" column="name"/>
        </collection>
    </resultMap>

    <select id="findUserByUsername" parameterType="string" resultMap="userMap">
        SELECT u.*,r.name FROM user u
        LEFT JOIN user_role ur ON u.id = ur.user_id
        LEFT JOIN role r ON ur.role_id = r.id
        WHERE username = #{username}
    </select>
    
</mapper>

8.前端頁面

admin.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>擁有許可權才能訪問該頁面</h3>
<p><a href="../../templates/article/list.html" th:href="@{/article}" >管理文章</a></p>
<p><a href="../../templates/index.html" th:href="@{/index}" >返回首頁</a></p>
<div th:substituteby="index::user"></div>
</body>
</html>

list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <title>list</title>
</head>
<body>
<h3>文章管理,ADMIN角色才能刪除</h3>
<table>
    <tr>
        <td><b>文章編號</b></td>
        <td><b>文章標題</b></td>
        <td><b>文章內容</b></td>
    </tr>
    <tr th:each="article:${articlesList}">
        <td th:text="${article.id}"></td>
        <td th:text="${article.title}"></td>
        <td th:text="${article.content}"></td>
        <td><a href="" th:href="@{'/article/'+${article.id}+'/deletion'}">刪除</a></td>
    </tr>
</table>
<div th:substituteby="index::user"></div>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>首頁任何角色都能訪問</h3>
<p><a href="../templates/admin/admin.html" th:href="@{/admin}">檢視被保護介面: /admin</a></p>

<div th:fragment="user" sec:authorize="isAuthenticated()">
    登入使用者:<span sec:authentication="name"></span>
    使用者角色:<span sec:authentication="principal.authorities"></span>
    <div>
        <form th:action="@{/logout}" method="post">
            <input type="submit" value="退出">
        </form>
    </div>
</div>

</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>登入頁面</h2>
<p th:if="${loginError}">使用者名稱或密碼錯誤</p>
<form th:action="@{/login}" method="post">
    <label for="username">使用者名稱</label>
    <input type="text" id="username" name="username" autofocus="autofocus" autocomplete="new-text">
    <label for="password">密碼</label>
    <input type="password" id="password" name="password" autocomplete="new-password">
    <input type="submit" value="login">
    <p><a href="/index" th:href="@{/index}">返回首頁</a></p>

</form>

</body>
</html>

9.資料庫如下

/*
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `role`
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'ROLE_ADMIN');
INSERT INTO `role` VALUES ('2', 'ROLE_TEACHER');
INSERT INTO `role` VALUES ('3', 'ROLE_STUDENT');
INSERT INTO `role` VALUES ('4', 'ROLE_COUNSELOR');

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `password` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_USERNAME` (`username`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'admin', '$2a$10$NmtmORbN/ATToou17gvjl.CUu1yTNxxRjsO2GOJUbJFsWd21pYmFi');
INSERT INTO `user` VALUES ('2', 'js', '$2a$10$NmtmORbN/ATToou17gvjl.CUu1yTNxxRjsO2GOJUbJFsWd21pYmFi');
INSERT INTO `user` VALUES ('3', 'xs', '$2a$10$NmtmORbN/ATToou17gvjl.CUu1yTNxxRjsO2GOJUbJFsWd21pYmFi');
INSERT INTO `user` VALUES ('4', 'fdy', '$2a$10$NmtmORbN/ATToou17gvjl.CUu1yTNxxRjsO2GOJUbJFsWd21pYmFi');

-- ----------------------------
-- Table structure for `user_role`
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `user_id` int(11) NOT NULL,
  `role_id` int(11) NOT NULL,
  KEY `FKuser_id` (`role_id`),
  KEY `FKrole_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', '1');
INSERT INTO `user_role` VALUES ('2', '2');
INSERT INTO `user_role` VALUES ('3', '3');
INSERT INTO `user_role` VALUES ('4', '4');

10.整個專案結構

在這裡插入圖片描述

3.測試結果

我們在 security 裡配置了 Index 頁面不需要驗證即可訪問,訪問
http://localhost:8081/index

在這裡插入圖片描述

在沒有登入之前,通過瀏覽器訪問其他任何頁面,都會跳轉到登入介面。
在這裡插入圖片描述
登入之後即可進入管理頁面,這裡的加密密碼都是123,賬號請看資料庫。
在這裡插入圖片描述
在管理文章裡面,我們在 controller 層配置了只有 ADMIN 使用者才能刪除,其他使用者如果點選刪除將會提示沒有許可權。
在這裡插入圖片描述

原始碼:https://github.com/huangliangyun/Spring-Boot-2.X
QQ交流群:865061230
參考<<深入理解SpringCloud微服務構建>>