1.实现目的:通过spring boot security ,jpa,mvc,themeleaf实现用户登录权限验证
2.pom.xml引入依赖及application.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>myspringboot2</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/world
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
3.准备数据库,以下分别为用户表,角色表,菜单权限表,用户角色关系表,角色菜单权限关系表:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`username` varchar(32) DEFAULT NULL COMMENT '用户名',
`password` varchar(128) DEFAULT NULL COMMENT '密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`name` varchar(64) DEFAULT NULL COMMENT '角色名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
CREATE TABLE `menu` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`name` varchar(64) DEFAULT NULL COMMENT '菜单或按键名称',
`url` varchar(500) DEFAULT NULL COMMENT '访问url',
`permission` varchar(500) DEFAULT NULL COMMENT '权限(ROLE_前缀)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
CREATE TABLE `user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`user_id` int(11) DEFAULT NULL COMMENT '用户id',
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
CREATE TABLE `role_menu` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
`menu_id` int(11) DEFAULT NULL COMMENT '菜单id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
3.对就上面五个表的实体类:
package com.example.jpa.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 用户表
*/
@Entity
@Table(name="user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
public Long getId() {
return id;
}
public void setId(Long 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;
}
}
package com.example.jpa.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 权限表
*/
@Entity
@Table(name = "role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name="name")
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.example.jpa.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 菜单表
*/
@Entity
@Table(name = "menu")
public class Menu {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name="name")
private String name;
@Column(name="url")
private String url;
@Column(name="permission")
private String permission;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
}
package com.example.jpa.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 用户角色表
*/
@Entity
@Table(name="user_role")
public class UserRole {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "user_id")
private Long userId;
@Column(name = "role_id")
private Long roleId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
}
package com.example.jpa.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 权限菜单表
*/
@Entity
@Table(name="role_menu")
public class RoleMenu {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name="role_id")
private Long roleId;
@Column(name="menu_id")
private Long menuId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getMenuId() {
return menuId;
}
public void setMenuId(Long menuId) {
this.menuId = menuId;
}
}
4.对应DAO 及Service方法
package com.example.jpa.dao;
import java.io.Serializable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description DAO基类
*/
@NoRepositoryBean
public interface BaseDao<T,ID extends Serializable> extends JpaRepository<T, ID> {
}
package com.example.jpa.dao;
import org.springframework.stereotype.Repository;
import com.example.jpa.entity.User;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 用户DAO
*/
@Repository
public interface UserDao extends BaseDao<User, Long>{
User findOneByUsername(String name);
}
package com.example.jpa.service;
import com.example.jpa.entity.User;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 描述
*/
public interface UserService {
void addUser(User u);
User findByName(String name);
}
package com.example.jpa.service;
import java.util.List;
import com.example.jpa.entity.Menu;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 描述
*/
public interface MenuService {
List<Menu> findByUserName(String username);
}
package com.example.jpa.service.impl;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.jpa.dao.UserDao;
import com.example.jpa.entity.User;
import com.example.jpa.service.UserService;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 描述
*/
@Service("userService")
@Transactional(readOnly=true)
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
@Transactional
public void addUser(User u) {
userDao.save(u);
}
@Override
public User findByName(String name) {
return userDao.findOneByUsername(name);
}
}
package com.example.jpa.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.jpa.dao.MenuDao;
import com.example.jpa.entity.Menu;
import com.example.jpa.service.MenuService;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 描述
*/
@Service("menuService")
@Transactional(readOnly=true)
public class MenuServiceImpl implements MenuService {
@Autowired
private MenuDao menuDao;
@Override
public List<Menu> findByUserName(String username) {
// TODO Auto-generated method stub
return menuDao.findByUsername(username);
}
}
5. spring boot 的junit测试,controller也可以用junit测试,小伙伴们可以自己偿试:
package com.example.jpatest;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.jpa.entity.Menu;
import com.example.jpa.entity.User;
import com.example.jpa.service.MenuService;
import com.example.jpa.service.UserService;
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
@ComponentScan(basePackages = "com.example.jpa.*")
public class UserRespositoryUnitTest {
@Autowired
private UserService userService;
@Autowired
private MenuService menuService;
/**
* 新增测试用户数据
*/
@Test
@Rollback(false)//为了可以用junit新增一些测试数据,如果不加这个注解,默认是会回滚的。
public void addUser(){
User u = new User();
u.setUsername("test");
// u.setPassword(Md5Utils.md5("123456"));
//直接可以在spring框架引用bcrypt加盐加密,相比MD5加密,bcrypt加密每次的密码后台显示都是唯一的,加密性更加强。
u.setPassword(BCrypt.hashpw("123456", BCrypt.gensalt()));
userService.addUser(u);
}
/**
* 测试菜单权限方法
*/
@Test
public void findByUsernamePermisions(){
List<Menu> menus =menuService.findByUserName("zzm");
System.out.println("menus>>>>>>>>>>>>>>>"+menus);
}
}
5.编写spring security 配置类,spring 中可以直接引用BCrypt加密,也可以自己定义选择加密方式匹配自己原系统的用户密码加密方法。
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 配置security权限验证
*/
@Configuration
public class BrowSecurityConfig extends WebSecurityConfigurerAdapter{
/**
* 定义登录入口
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.formLogin() // 定义当需要用户登录时候,转到的登录页面。
.loginPage("/user/login") // 设置登录页面
.loginProcessingUrl("/user/dologin") // 自定义的登录接口
//.defaultSuccessUrl("/user/dologin")
.and()
.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/user/login").permitAll() // 设置所有人都可以访问登录页面
.anyRequest() // 任何请求,登录后可以访问
.authenticated()
.and()
.csrf().disable(); // 关闭csrf防护
}
/**
* bcry加密验证,可以重写用自己定义的加密方法
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
//return new MyMd5PasswordEncoder(); 自定义的md5数据加密
}
}
6.实现spring security 安全验证UserdetailsService接口:
package com.example.config;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import com.example.jpa.entity.Menu;
import com.example.jpa.service.MenuService;
import com.example.jpa.service.UserService;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 实现security 的 UserDetailsService接口,引入自己的后台权限数据库
*/
@Component
public class MyUserDetailService implements UserDetailsService{
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private UserService userService;
@Autowired
private MenuService menuService;
/**
* spring security 安全验证用户方法
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
com.example.jpa.entity.User u = userService.findByName(username);//自己系统的用户类
User user = null;
if(null==u){
throw new UsernameNotFoundException("找不到该账户信息!");
}
else{
//spring security 管理的用户类,只需要将用户名,密码,及角色列表数据交给spring就可以了
user = new User(username, u.getPassword(), findPermisions(username));
}
return user;
}
/**
* 根据用户名去确定用户拥有菜单权限
* @param username
* @return
*/
public List<GrantedAuthority> findPermisions(String username){
List<GrantedAuthority> list = new ArrayList<>();
List<Menu> menus = menuService.findByUserName(username);
for (Menu menu : menus) {
list.add(new SimpleGrantedAuthority(menu.getPermission()));
}
return list;
}
}
7.实现的controller
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.jpa.entity.User;
/**
*
* @author 钟之鸣
* @time 2018年7月11日
* @description 定义一个普通用户登录及登录验证的类
*/
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 登录入口
* @param user
* @return
*/
@RequestMapping("/login")
public String login(User user){
return "/user/login";
}
/**
* 登录后
* @return
*/
@RequestMapping("/dologin")
public String dologin(){
return "/user/test";
}
}
8.themeleaf简单登录页面,详细标签请自行参考开发文档:
登录页面:
<!DOCTYPE>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>自定义登录页面</h2>
<form th:action="@{/user/dologin}" th:method="post" th:object="${user}">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" th:field="*{username}"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" th:field="*{password}"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</body>
</html>
登录成功后测试权限标签的页面:<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<p>登录成功后测试页面!</p>
<p>你拥有的权限如下:</p><br>
<div sec:authorize="hasRole('ROLE_TEST_1')">菜单1</div>
<div sec:authorize="hasRole('ROLE_TEST_2')">菜单2</div>
<div sec:authorize="hasRole('ROLE_TEST_3')">菜单3</div>
</body>
</html>
这里要注意POM一定要引入springsecurity4依赖,否则sec:authorize标签无效。
9.实现结果,根据用户登录控制显示菜单权限,整个代码结构如下:
10.