1. 程式人生 > >小白的springboot之旅(十一)

小白的springboot之旅(十一)

關鍵詞:springboot,jpa,spring security,mysql

通常我們的網站都有許可權控制,就像一個公司有產品、開發、運維之分,各自負責各自的業務,相互獨立,有相互協作,共同完成一個任務。擁有不同許可權的使用者檢視不同的頁面,進行不同的操作。

這篇來簡單的說一下使用springboot+jpa+springsecurity實現簡單的使用者許可權管理。

角色和使用者的關係通過資料庫配置控制。角色可以訪問的許可權通過硬編碼控制。

springboot是一個靈活和強大的身份驗證和訪問控制框架,以確保基於spring的java web應用程式的安全。它與spring mvc有很好地整合,並配備了流行的安全演算法實現捆綁在一起。

1)    springscurity驗證使用者密碼機制

首先在usernamePasswordAuthenticationFilter中來攔截登入請求,並呼叫AuthenticationManager。AuthenticationManager呼叫Provider,provider呼叫userDetaisService來根據username獲取真實的資料庫資訊。 

2) 資料庫設計

主要包括:使用者表、角色表、使用者關係表。

3) 新增spring security的maven依賴

<dependency>
   <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> </dependency>

4)編寫實體

User.java實現UserDetails介面,通過getAuthorities獲取使用者的角色。

package com.xiaoi.document.split.security.user.entity;
import com.xiaoi.document.split.security.role.entity.Role;
import lombok.ToString;
import org.hibernate.annotations.Cache
; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import javax.persistence.*; import java.util.Collection; import java.util.List; @Entity @Table(name = "sys_user") @Cacheable @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "entityCache") @ToString(includeFieldNames = true) public class User implements UserDetails { @Id @GeneratedValue @Column(name = "id") private String id; @Column(name = "username") private String username; @Column(name = "password") private String password; @ManyToMany(targetEntity = Role.class, cascade = {CascadeType.REFRESH}, fetch = FetchType.EAGER) @JoinTable(name = "sys_role_user", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) private List<Role> roles; public User() { } public User(String id, String username, String password) { this.id = id; this.username = username; this.password = password; } public String getId() { return id; } public void setId(String 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; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return roles; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }

role.java

package com.xiaoi.document.split.security.role.entity;
import com.xiaoi.document.split.security.permission.entity.Permission;
import com.xiaoi.document.split.security.user.entity.User;
import lombok.ToString;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.ManyToAny;
import org.springframework.security.core.GrantedAuthority;
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "sys_role")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "entityCache")
@ToString(includeFieldNames = true)
public class Role implements GrantedAuthority {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private String id;
@Column(name = "name")
    private String name;
@ManyToMany(targetEntity = Permission.class, cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
    @JoinTable(name = "sys_permission_role", joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "permission_id", referencedColumnName = "id"))
    private List<Permission> permissions;
    public Role() {
    }

    public String getId() {
        return id;
}

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

    public String getName() {
        return name;
}

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

    public List<Permission> getPermissions() {
        return permissions;
}

    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
}

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

5) 自定義使用者驗證service

UserDAO.java

package com.xiaoi.document.split.security.user.service.impl;
import com.xiaoi.document.split.security.user.dao.UserDAO;
import com.xiaoi.document.split.security.user.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service(value = "customerUserService")
public class CustomerUserService implements UserDetailsService {

    @Autowired
private UserDAO userDAO;
@Override
public UserDetails loadUserByUsername(String username) {
        User user = userDAO.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("使用者名稱不存在");
}
        String pwd = new BCryptPasswordEncoder().encode(user.getPassword());
user.setPassword(pwd);
        return user;
}
}
CustomerService.java
package com.xiaoi.document.split.security.user.service.impl;
import com.xiaoi.document.split.security.user.dao.UserDAO;
import com.xiaoi.document.split.security.user.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service(value = "customerUserService")
public class CustomerUserService implements UserDetailsService {

    @Autowired
private UserDAO userDAO;
@Override
public UserDetails loadUserByUsername(String username) {
        User user = userDAO.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("使用者名稱不存在");
}
        String pwd = new BCryptPasswordEncoder().encode(user.getPassword());
user.setPassword(pwd);
        return user;
}
}

controller

package com.xiaoi.document.split.security.home.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
 * Created by Administrator on 2018/5/24.
 */
@Controller
public class HomeCotroller {

//    @RequestMapping(value = "/", method = RequestMethod.GET)
//    String home() {
//        return "login";
//    }
//
//    @RequestMapping(value = "/login", method = RequestMethod.GET)
//    String login() {
//        return "login";
//    }
//    @RequestMapping(value = "/welcome", method = RequestMethod.GET)
//    String welcome() {
//        return "welcome";
//    }
//    @RequestMapping(value = "/loginSubmit", method = RequestMethod.GET)
//    String loginSubmit(Model model, User user) {
//        model.addAttribute("user", user);
//        return "index";
//    }
@RequestMapping("/index")
    public String index(Model model) {
//        Msg msg =  new Msg("測試標題","測試內容","歡迎來到HOME頁面,您擁有 ROLE_ADMIN 許可權");
//        model.addAttribute("msg", msg);
return "index";
}

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

6) 新增檢視,配置安全策略

package com.xiaoi.document.split.security.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * WebMvcConfig 頁面訪問安全配置
 *
 * @Author Yuan Jingshan
 * @Date 2018-05-29
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
}
}
package com.xiaoi.document.split.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
 * WebMvcSecurityConfig 頁面訪問安全配置
 *
 * @Author Yuan Jingshan
 * @Date 2018-05-29
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
private AuthorizationSecurityInterceptor authorizationSecurityInterceptor;
//註冊UserDetailsService的bean
@Autowired
UserDetailsService customerUserService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customerUserService).passwordEncoder(new BCryptPasswordEncoder());
}

    //安全策略
@Override
protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/register", "/reg", "/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .failureUrl("/login?error")
                .defaultSuccessUrl("/index")
                .permitAll()
                .and()
                .logout()
                .permitAll();
http.addFilterBefore(authorizationSecurityInterceptor, FilterSecurityInterceptor.class);
}

    //解決靜態資源被攔截的問題
@Override
public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/img/**");
web.ignoring().antMatchers("/plugins/**");
}
}

7)前端頁面

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>springboot-demo</title>
    <link rel="Shortcut Icon" href="/img/logo_m.png" type="image/x-icon">
    <link rel="stylesheet" th:href="@{plugins/layui/css/layui.css}">
    <link rel="stylesheet" th:href="@{css/style.css}">
</head>
<body class="login">
<div class="layui-layout">
<!-- 主體 -->
<div class="layui-body">
        <div class="layui-row" style="margin-top: 5%;margin-bottom: 5%;">
            <div class="layui-col-md7"
style="border-bottom: 2px solid white;margin-right: 2%;letter-spacing:2px;float: right;">
                <div class="project_name">
                    <img src="/img/logo_l.png"/>
                    <strong>QA對知識拆分管理系統</strong>
                </div>
            </div>
        </div>
        <div class="layui-row">
            <div class="layui-col-md3 layui-col-md-offset8">
                <div class="layui-input-block"
style="text-align: center;line-height: 38px;background-color: #f79647;margin-bottom:5px;font-weight: 800;color: white;letter-spacing:5px;border-radius:5px">
使用者登入
                </div>
                <form class="layui-form" th:action="@{/login}" action="/login" method="POST"
style="background-color: white;padding: 10px 10px;border-radius:5px">
                    <div class="layui-form-item" style="margin-top: 10px;background-color: white;">
                        <div class="layui-input-block">
                            <input type="text" id="username" name="username" lay-verify="title" autocomplete="off" placeholder="請輸入使用者名稱" class="layui-input">
                        </div>
                    </div>
                    <div class="layui-form-item">
                        <div class="layui-input-block">
                            <input type="password" id="password" name="password" lay-verify="required" placeholder="請輸入密碼" autocomplete="off" class="layui-input">
                        </div>
                    </div>
                    <div id="login_div" class="layui-form-item">
                        <div class="layui-input-block">
                            <p th:if="${param.logout}" class="bg-warning">已成功登出</p>
                            <p th:if="${param.error}" class="bg-danger">有錯誤,請重試</p>
                            <button class="layui-btn" lay-submit lay-filter="formDemo">登入</button>
                        </div>
<!--<input type="button" id="login_btn" class="layui-btn layui-btn-primary layui-col-xs-offset9"-->
                        <!--style="padding: 0 23px;border: 1px solid #4476A7;color: #B2B2B2;border-radius:10px"-->
                        <!--value="登入">-->
                        <!--</input>-->
</div>
                </form>
            </div>
        </div>
    </div>
    <div class="layui-footer">
        <p>
© 貴州小愛機器人科技有限公司
        </p>
    </div>
</div>
<script th:src="@{plugins/layui/layui.js}"></script>
<script>
layui.use('form', function () {
        var form = layui.form;
//        //監聽提交
//        form.on('submit(formDemo)', function (data) {
//            layer.msg(JSON.stringify(data.field));
//            return false;
//        });
});
</script>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
     xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
   <meta content="text/html;charset=UTF-8"/>
   <title sec:authentication="name"></title>
   <link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
   <style type="text/css">
      body {
         padding-top: 50px;
      }
      .starter-template {
         padding: 40px 15px;
         text-align: center;
      }
   </style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
   <div class="container">
      <div class="navbar-header">
         <a class="navbar-brand" href="#">Spring Security演示</a>
      </div>
      <div id="navbar" class="collapse navbar-collapse">
         <ul class="nav navbar-nav">
            <li><a th:href="@{/}"> 首頁 </a></li>
            <li><a th:href="@{/admin}"> admin </a></li>
         </ul>
      </div><!--/.nav-collapse -->
   </div>
</nav>


<div class="container">

   <div class="starter-template">
      <h1 th:text="${msg.title}"></h1>

      <p class="bg-primary" th:text="${msg.content}"></p>

      <div sec:authorize="hasRole('ROLE_ADMIN')"> <!-- 使用者型別為ROLE_ADMIN 顯示 -->
         <p class="bg-info" th:text="${msg.extraInfo}"></p>
      </div>

      <div sec:authorize="hasRole('ROLE_USER')"> <!-- 使用者型別為 ROLE_USER 顯示 -->
         <p class="bg-info">恭喜您,您有 ROLE_USER 許可權</p>
      </div>

      <form th:action="@{/logout}" method="post">
         <input type="submit" class="btn btn-primary" value="登出"/>
      </form>
   </div>

</div>

</body>
</html>