小白的springboot之旅(十一)
阿新 • • 發佈:2019-01-08
關鍵詞: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>