通常我们的网站都有权限控制,就像一个公司有产品、开发、运维之分,各自负责各自的业务,相互独立,有相互协作,共同完成一个任务。拥有不同权限的用户查看不同的页面,进行不同的操作。
这篇来简单的说一下使用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>