使用Spring Boot + JPA + SpringSecurity 实现用户权限数据库管理
开发工具 STS(Spring Tool Suite) spring boot 2.0.6
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
runtime
org.springframework.security
spring-security-test
test
org.springframework.boot
spring-boot-devtools
true
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/dome2
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
数据设计
用户(user) 、角色 (role) 、 权限(authority ) 用数据库存储权限 ,url 存储在权限表中,其中用户 、角色 多对多关系, 角色 、权限也是多对多关系
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL,
`email` varchar(50) DEFAULT NULL,
`enabled` bit(1) DEFAULT NULL,
`firstname` varchar(50) DEFAULT NULL,
`lastpasswordresetdate` datetime DEFAULT NULL,
`lastname` varchar(50) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`username` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES (1,'[email protected]',b'1','admin',NULL,'admin','123','sjy'),(2,'[email protected]',b'1','user',NULL,'user','123','user'),(3,'[email protected]',b'0','user',NULL,'user','123','test');
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(25) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `role` VALUES (1,'ROLE_ADMIN'),(2,'ROLE_USER'),(3,'ROLE_TEST');
DROP TABLE IF EXISTS `authority`;
CREATE TABLE `authority` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`pid` int(11) NOT NULL,
`url` varchar(255) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `authority` VALUES (1,'admin',0,'/index1','管理'),(2,'user',0,'/index2','用戶'),(3,'test',0,'/index3','測試');
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`user_id` bigint(20) NOT NULL,
`role_id` bigint(20) NOT NULL,
KEY `FKa68196081fvovjhkek5m97n3y` (`role_id`),
KEY `FK859n2jvi8ivhui0rl0esws6o` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `user_role` VALUES (1,1),(2,2),(3,3),(1,2);
DROP TABLE IF EXISTS `role_auth`;
CREATE TABLE `role_auth` (
`auth_id` bigint(20) NOT NULL,
`role_id` bigint(20) NOT NULL,
KEY `FKrcrl3r3y8xexlss4vuah9h5pj` (`role_id`),
KEY `FKdclw9v91w4q8voap572llxar1` (`auth_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `role_auth` VALUES (1,1),(2,3),(2,3),(2,2),(3,3);
package com.sjy.bean;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Entity
@Table(name = "USER")
public class User implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "USERNAME", length = 50, unique = true)
private String username;
@Column(name = "PASSWORD", length = 100)
private String password;
@Column(name = "FIRSTNAME", length = 50)
private String firstname;
@Column(name = "LASTNAME", length = 50)
private String lastname;
@Column(name = "EMAIL", length = 50)
private Boolean enabled;
@Column(name = "LASTPASSWORDRESETDATE")
@Temporal(TemporalType.TIMESTAMP)
private Date lastPasswordResetDate;
//急加载 会查询role表
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "USER_ROlE",
joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")},
inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")})
private List roles;
//属性的get set 方法略················
//实现UserDetails 重写的方法
@Override
public Collection extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
List auths = new ArrayList<>();
List roles = this.getRoles();
for (Role role : roles) {
for(Authority aurh:role.getAuthoritys())
auths.add(new SimpleGrantedAuthority(aurh.getName()));
}
return auths;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
}
package com.sjy.bean;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = "role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(length = 25)
private String name;
//懒加载 不会查询role表
@ManyToMany(mappedBy = "roles",fetch = FetchType.LAZY)
private List users;
//急加载 会查询role表
@ManyToMany(mappedBy = "roles",fetch = FetchType.EAGER)
private List Authoritys;
//get set方法略·········
}
package com.sjy.bean;
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "AUTHORITY")
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)//主键自动生成
@Column(name="ID")
private Long id;
private String name;
private String url;
private int pid;
private String description;
@ManyToMany(fetch = FetchType.LAZY)//懒加载 快速查询 不会查询role表
@JoinTable(
name = "ROlE_Auth",
joinColumns = {@JoinColumn(name = "auth_ID", referencedColumnName = "ID")},
inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")})
private List roles;
//get set 略················
}
package com.sjy.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.sjy.bean.User;
public interface userdao extends JpaRepository {
public User findByUsername(String name);
}
此类 实现 UserDetailsService接口 重写LoadUserUsername方法。
首先在usernamePasswordAuthenticationFilter中来拦截登录请求,并调用AuthenticationManager。AuthenticationManager调用
Provider,provider调用userDetaisService来根据username获取真实的数据库信息。最终验证帐号密码的类是
org.springframework.security.authentication.dao.DaoAuthenticationProvider.。
注意的是这里只需要一个根据用户名查询出用户的方法即可,不需要通过用户名和密码去查询。
package com.sjy.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.core.SpringSecurityMessageSource;
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.Service;
import com.sjy.bean.User;
import com.sjy.dao.userdao;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private userdao userdao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userdao.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
System.out.println(user.getAuthorities());
return user;
}
}
package com.sjy.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login1").setViewName("login");
registry.addViewController("/index1").setViewName("index1");
registry.addViewController("/index2").setViewName("index2");
registry.addViewController("/index3").setViewName("index3");
registry.addViewController("/myerror").setViewName("myerror");
}
}
package com.sjy.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.sjy.security.MyPasswordEncoder;
import com.sjy.security.UserDetailsServiceImpl;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
UserDetailsService customUserService() {
return new UserDetailsServiceImpl();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(customUserService()).passwordEncoder(new MyPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and().formLogin().loginPage("/login1")
//设置默认登录成功跳转页面
.defaultSuccessUrl("/main").failureUrl("/login1?error").permitAll()
.and()
.logout()
//默认注销行为为logout,可以通过下面的方式来修改
.logoutUrl("/custom-logout")
//设置注销成功后跳转页面,默认是跳转到登录页面
.logoutSuccessUrl("/login1")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/myerror");//无权访问;
}
}
package com.sjy.security;
import org.springframework.security.crypto.password.PasswordEncoder;
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence arg0) {
return arg0.toString();
}
@Override
public boolean matches(CharSequence arg0, String arg1) {
return arg1.equals(arg0.toString());
}
}
package com.sjy.controller;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.sjy.bean.Role;
import com.sjy.bean.User;
import com.sjy.dao.authdao;
@Controller
public class HomeController {
@Autowired
private authdao authdao;
@RequestMapping("/main")
public String main(Model model) {
User user =getUserDetails();
Set set = new HashSet();
List roles = user.getRoles();
for (Role role : roles) {
set.addAll(role.getAuthoritys());
}System.out.println(set);
model.addAttribute("authorities",set);
return "index";
}
private User getUserDetails() {
SecurityContext ctx = SecurityContextHolder.getContext();
Authentication auth = ctx.getAuthentication();
return (User)auth.getPrincipal();
}
}
登录
已注销
有错误,请重试
使用账号密码登录
后台登陆页面
那些好的东西都是你的勒
后台登陆页面
myerror.html
無權訪問
package com.sjy.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;
import com.sjy.bean.Authority;
import com.sjy.dao.authdao;
@Service
public class MyInvocationSecurityMetadataSourceService implements
FilterInvocationSecurityMetadataSource{
@Autowired
private authdao authdao;
private HashMap> map=null;
/*
* 系统启动后,首次有用户访问,加载权限表中所有权限。以便拦截无权放访问的用户请求。
*
* wzh增加了注释描述。
*/
public void loadResourceDefine(){
map=new HashMap<>();
Collection array;
ConfigAttribute cfg;
List permissions=authdao.findAll();
for(Authority permission:permissions){
array = new ArrayList<>();
cfg=new SecurityConfig(permission.getName());
//此处只添加了权限的名字,其实还可以添加更多权限的信息,例如请求方法到ConfigAttribute的集合中去。
array.add(cfg);
//用权限的getUrl()作为map的key,用ConfigAttribute的集合作为value
map.put(permission.getUrl(), array);
}
}
/*
* 此方法是为了判定用户请求的url是否再权限表中,如果在权限表中,则返回给decide方法,
* 用来判定用户是否有此权限。如果不在权限表中则放行。
*
* 方法的目的是:确定该请求是否需要进行访问权限的判断,对于需要判断权限的请求,返回resUrl,对于不需要
* 进行权限判断的请求,返回Null
*/
@Override
public Collection getAttributes(Object object) throws IllegalArgumentException {
if(map ==null) loadResourceDefine();
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
AntPathRequestMatcher matcher;
String resUrl;
for(Iterator iter = map.keySet().iterator(); iter.hasNext(); ) {
resUrl = iter.next();
//当url里有?的时候 进行切割
if(resUrl.indexOf("?")>-1){
resUrl=resUrl.substring(0,resUrl.indexOf("?"));
}
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
return map.get(resUrl);
}
}
return null; //
}
@Override
public Collection getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class> clazz) {
return true;
}
}
package com.sjy.security;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter{
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}
package com.sjy.security;
import org.springframework.security.access.AccessDeniedException;
import java.util.Collection;
import java.util.Iterator;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
@Service
public class MyAccessDecisionManager implements AccessDecisionManager{
//decide 方法是判定是否拥有权限的决策方法
//authentication是UserDetailsServiceImpl中循环添加到GrantedAuthority 对象中的权限信息集合
//object包含客户端发起的请求的request信息,可转换为 HttpServletRequest request=((FilterInvocation) object)
//.getHttpRequest();
//configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,
//此方法是为了判定用户请求的Url 是否在权限表中,如果在权限表中,则返回decide方法,用来判定用户是否由此权限,如果不在权限表中则放行。
@Override
public void decide(
Authentication authentication,
Object object,
Collection configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if(null== configAttributes || configAttributes.size() <=0) {
return;
//说明请求的系统中不存在指定的URL,返回执行security配置文件中其他项目。
}
ConfigAttribute c;
String needRole;
for(Iterator iter = configAttributes.iterator(); iter.hasNext(); ) {
c = iter.next();
needRole = c.getAttribute();
for(GrantedAuthority ga : authentication.getAuthorities()) {
if(needRole.trim().equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException(" 没有权限访问! ");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class> clazz) {
return true;
}
}
添加 页面 测试权限使用 index1.html index2.html index3.html
package com.sjy.controller;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.sjy.bean.Role;
import com.sjy.bean.User;
import com.sjy.dao.authdao;
@Controller
public class HomeController {
@Autowired
private authdao authdao;
@RequestMapping("/main")
public String main(Model model) {
/*User user =getUserDetails();
Set set = new HashSet();
List roles = user.getRoles();
for (Role role : roles) {
set.addAll(role.getAuthoritys());
}System.out.println(set);*/
model.addAttribute("authorities",authdao.findAll());
return "index";
}
private User getUserDetails() {
SecurityContext ctx = SecurityContextHolder.getContext();
Authentication auth = ctx.getAuthentication();
return (User)auth.getPrincipal();
}
}
使用sjy用户登录 他有管理 用户 权限的 但是没有 测试的权限
借鉴
https://blog.csdn.net/u012373815/article/details/54632176
https://blog.csdn.net/u012702547/article/details/54319508
感谢感谢
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------更新 : 发现了问题 :
1.当你登陆之后其实所有的接口都是可以请求的。 () 在MyInvocationSecurityMetadataSourceService.java getAttributes方法当你登陆之后 return null 是默认通过的 就是 只要是你数据库没有的接口权限都是给你默认通过的。 反映了 “防君子不妨小人” 将所有的controller的接口映射都放入了数据库。一些公用的接口给定一样的权限
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
添加qq第三方登录功能2019/3