权限控制(springboot整合security实现权限控制)
1.建表,五张表,如下:
1.1.用户表
CREATE TABLE `t_sys_user` (`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',`user_name` varchar(30) NOT NULL COMMENT '用户名',`user_password` varchar(128) NOT NULL COMMENT '用户密码',`salt` varchar(64) DEFAULT NULL COMMENT '加密盐',`user_phone` varchar(20) DEFAULT NULL COMMENT '手机号',`user_emai` varchar(20) DEFAULT NULL COMMENT '邮箱',`user_title` varchar(20) DEFAULT NULL COMMENT '职称',`creater_id` bigint(20) DEFAULT NULL COMMENT '创建人ID',`creater_name` varchar(30) DEFAULT NULL COMMENT '创建人名称',`creater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updater_id` bigint(20) DEFAULT NULL COMMENT '更新人ID',`updater_name` varchar(30) DEFAULT NULL COMMENT '更新人名称',`updater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',`role_ids` varchar(200) DEFAULT NULL,`role_names` varchar(300) DEFAULT NULL,PRIMARY KEY (`user_id`)) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
1.2.用户角色表
CREATE TABLE `t_sys_user_role` (`user_role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户角色ID',`user_id` bigint(20) NOT NULL COMMENT '用户ID',`role_id` bigint(20) NOT NULL COMMENT '角色ID',PRIMARY KEY (`user_role_id`)) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8;
1.3.角色表
CREATE TABLE `t_sys_role` (`role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',`role_name` varchar(100) NOT NULL COMMENT '角色名称',`role_code` varchar(100) NOT NULL COMMENT '角色编码',`creater_id` bigint(20) DEFAULT NULL COMMENT '创建人ID',`creater_name` varchar(30) DEFAULT NULL COMMENT '创建人名称',`creater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updater_id` bigint(20) DEFAULT NULL COMMENT '更新人ID',`updater_name` varchar(30) DEFAULT NULL COMMENT '更新人名称',`updater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',`permission_ids` varchar(200) DEFAULT NULL,`permission_names` varchar(300) DEFAULT NULL,PRIMARY KEY (`role_id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
1.4.角色权限表
CREATE TABLE `t_sys_role_permission` (`role_permission_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色权限ID',`role_id` bigint(20) NOT NULL COMMENT '角色ID',`permission_id` bigint(20) NOT NULL COMMENT '权限ID',PRIMARY KEY (`role_permission_id`)) ENGINE=InnoDB AUTO_INCREMENT=78 DEFAULT CHARSET=utf8;
1.5.权限表
CREATE TABLE `t_sys_permission` (`permission_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '权限ID',`permission_name` varchar(100) NOT NULL COMMENT '权限名称',`permission_code` varchar(100) NOT NULL COMMENT '权限编码',`creater_id` bigint(20) DEFAULT NULL COMMENT '创建人ID',`creater_name` varchar(30) DEFAULT NULL COMMENT '创建人名称',`creater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updater_id` bigint(20) DEFAULT NULL COMMENT '更新人ID',`updater_name` varchar(30) DEFAULT NULL COMMENT '更新人名称',`updater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`permission_id`)) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
2.pom.xml引入依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
3.编码步骤:
3.1.在用户实体类中实现UserDetails接口的方法
package com.lz.hehuorenservice.system.entity;import com.lz.hehuorenservice.common.entity.BaseEntity;import io.swagger.annotations.ApiModelProperty;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.util.*;/** Create by hyhweb on 2021/6/6 16:24 */public class User extends BaseEntity implements UserDetails {/** 用户主键ID */@ApiModelProperty(value = "用户主键ID")private Long userId;/** 用户名 */@ApiModelProperty(value = "用户名")private String userName;/** 用户密码 */@ApiModelProperty(value = "用户密码")private String userPassword;@ApiModelProperty(value = "")private String salt;/** 手机号 */@ApiModelProperty(value = "手机号")private String userPhone;/** 邮箱 */@ApiModelProperty(value = "邮箱")private String userEmai;/** 职称 */@ApiModelProperty(value = "职称")private String userTitle;@ApiModelProperty(value = "角色ID")private String roleIds;@ApiModelProperty(value = "角色名称")private String roleNames;/** 创建人ID */@ApiModelProperty(value = "创建人ID")private Long createrId;/** 创建人名称 */@ApiModelProperty(value = "创建人名称")private String createrName;/** 创建时间 */@ApiModelProperty(value = "创建时间")private Date createrTime;/** 更新人ID */@ApiModelProperty(value = "更新人ID")private Long updaterId;/** 更新人名称 */@ApiModelProperty(value = "更新人名称")private String updaterName;/** 更新时间 */@ApiModelProperty(value = "更新时间")private Date updaterTime;private Set<String> permissions;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<SimpleGrantedAuthority> authorities = new ArrayList<>();/*//绑定角色的授权方法if(roles !=null){for (Role sysRole : roles) {authorities.add(new SimpleGrantedAuthority(sysRole.getRoleCode()));}}*/// 绑定权限的授权方法if (permissions != null) {for (String permission : permissions) {authorities.add(new SimpleGrantedAuthority(permission));}}return authorities;}@Overridepublic String getPassword() {return userPassword;}@Overridepublic String getUsername() {return userName;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}public Long getUserId() {return userId;}public void setUserId(Long userId) {this.userId = userId;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getUserPassword() {return userPassword;}public void setUserPassword(String userPassword) {this.userPassword = userPassword;}public String getSalt() {return salt;}public void setSalt(String salt) {this.salt = salt;}public String getUserPhone() {return userPhone;}public void setUserPhone(String userPhone) {this.userPhone = userPhone;}public String getUserEmai() {return userEmai;}public void setUserEmai(String userEmai) {this.userEmai = userEmai;}public String getUserTitle() {return userTitle;}public void setUserTitle(String userTitle) {this.userTitle = userTitle;}public String getRoleIds() {return roleIds;}public void setRoleIds(String roleIds) {this.roleIds = roleIds;}public String getRoleNames() {return roleNames;}public void setRoleNames(String roleNames) {this.roleNames = roleNames;}public Long getCreaterId() {return createrId;}public void setCreaterId(Long createrId) {this.createrId = createrId;}public String getCreaterName() {return createrName;}public void setCreaterName(String createrName) {this.createrName = createrName;}public Date getCreaterTime() {return createrTime;}public void setCreaterTime(Date createrTime) {this.createrTime = createrTime;}public Long getUpdaterId() {return updaterId;}public void setUpdaterId(Long updaterId) {this.updaterId = updaterId;}public String getUpdaterName() {return updaterName;}public void setUpdaterName(String updaterName) {this.updaterName = updaterName;}public Date getUpdaterTime() {return updaterTime;}public void setUpdaterTime(Date updaterTime) {this.updaterTime = updaterTime;}public Set<String> getPermissions() {return permissions;}public void setPermissions(Set<String> permissions) {this.permissions = permissions;}}
3.2.在用户的服务实现类中,实现UserDetailsService接口的loadUserByUsername方法,返回用户的所有信息。
package com.lz.hehuorenservice.system.service.impl;import com.lz.hehuorenservice.common.service.impl.BaseServiceImpl;import com.lz.hehuorenservice.system.dao.UserDao;import com.lz.hehuorenservice.system.entity.User;import com.lz.hehuorenservice.system.service.UserService;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.stereotype.Service;import java.util.Set;/** Create by hyhweb on 2021/6/6 16:28 */@Servicepublic class UserServiceImpl extends BaseServiceImpl<User, Long>implements UserService, UserDetailsService {@Autowired UserDao userDao;@Overridepublic UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {User user = userDao.getUserByName(userName);if (user == null) {throw new UsernameNotFoundException("账户不存在");}Set<String> permissions = userDao.getPermissionByUserId(user.getUserId());user.setPermissions(permissions);return user;}}
3.3.编写配置类,重写WebSecurityConfigurerAdapter类的三个configure方法,也就是重新配置三个对象AuthenticationManagerBuilder,HttpSecurity,WebSecurity。
package com.lz.hehuorenservice.common.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.lz.hehuorenservice.common.bean.CustomAccessDeniedHandler;import com.lz.hehuorenservice.common.bean.CustomAuthenticationEntryPoint;import com.lz.hehuorenservice.common.filter.CustomAuthenticationFilter;import com.lz.hehuorenservice.system.entity.User;import com.lz.hehuorenservice.system.service.impl.UserServiceImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.*;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.builders.WebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.access.AccessDeniedHandler;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.security.web.authentication.logout.LogoutHandler;import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import org.springframework.web.cors.CorsUtils;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.util.HashMap;import java.util.Map;/** Create by hyhweb on 2021/6/7 8:26 */@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowired UserServiceImpl userService; // 这个必须是接口的实现类,不能是接口@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(10);// return NoOpPasswordEncoder.getInstance();}/* @BeanRoleHierarchy roleHierarchy() {RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();// String hierarchy = "ROLE_dba> ROLE_admin \n ROLE_admin > ROLE_user";String hierarchy = "ROLE_admin > ROLE_user";roleHierarchy.setHierarchy(hierarchy);return roleHierarchy;}*/@BeanCustomAuthenticationFilter customAuthenticationFilter() throws Exception {CustomAuthenticationFilter filter = new CustomAuthenticationFilter();filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth)throws IOException, ServletException {Object principal = auth.getPrincipal();resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();resp.setStatus(200);Map<String, Object> map = new HashMap<>();map.put("code", "1");map.put("success", true);map.put("message", "登录成功");User user = (User) principal;user.setUserPassword(null);map.put("data", user);ObjectMapper om = new ObjectMapper();out.write(om.writeValueAsString(map));out.flush();out.close();/* resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();Map<String,Object> map = new HashMap<String,Object>();map.put("message", "登录成功");out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();*/}});filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {@Overridepublic void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e)throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();resp.setStatus(401);Map<String, Object> map = new HashMap<>();map.put("status", 401);if (e instanceof LockedException) {map.put("msg", "账号被锁定,登录失败");} else if (e instanceof BadCredentialsException) {map.put("msg", "账号或密码输入错误,请重新登录");} else if (e instanceof DisabledException) {map.put("msg", "账号被禁用,登录失败");} else if (e instanceof AccountExpiredException) {map.put("msg", "账号过期,登录失败");} else if (e instanceof CredentialsExpiredException) {map.put("msg", "密码过期,登录失败");} else {map.put("msg", "登录失败");}ObjectMapper om = new ObjectMapper();out.write(om.writeValueAsString(map));out.flush();out.close();/*resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();Map<String,Object> map = new HashMap<String,Object>();map.put("message", "登录失败");out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();*/}});filter.setAuthenticationManager(authenticationManagerBean());return filter;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService);}@Beanpublic AccessDeniedHandler getAccessDeniedHandler() {return new CustomAccessDeniedHandler();}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/sessionInvalid", "/register", "/app/**", "/login_page").antMatchers("/index.html", "/static/**", "/favicon.ico").antMatchers("/swagger-ui/**","/swagger/**","/doc.html","/swagger-resources/**","/images/**","/webjars/**","/v3/api-docs","/configuration/ui","/configuration/security");}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors() // 开启跨域.and() // 获取一个安全编译器.authorizeRequests() // 授权请求.requestMatchers(CorsUtils::isPreFlightRequest).permitAll() // 跨域的请求开放所有权限.anyRequest() // 所有请求.authenticated() // 所有请求都需要认证.and().sessionManagement().invalidSessionUrl("/session/invalid").and()// 获取一个安全编译器.formLogin()// 表单登录配置.loginPage("/login_page")// 登录页面访问地址.loginProcessingUrl("/login")// 配置登录接口地址.usernameParameter("userName")// 配置登录的账号字段.passwordParameter("userPassWord")// 配置登录密码字段.and()// 获取一个安全编译器.logout()// 退出登录配置.logoutUrl("/logout")// 设置退出登录的接口地址.clearAuthentication(true)// 清除所有认证信息.invalidateHttpSession(true)// 让session失效.addLogoutHandler(new LogoutHandler() {// 退出登录时的处理器@Overridepublic void logout(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Authentication authentication) {}}).logoutSuccessHandler(new LogoutSuccessHandler() {// 退出成功后的处理器@Overridepublic void onLogoutSuccess(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Authentication authentication)throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=utf-8");PrintWriter out = httpServletResponse.getWriter();Map<String, Object> map = new HashMap<>();map.put("message", "退出成功");map.put("code", "1");map.put("success", true);ObjectMapper om = new ObjectMapper();out.write(om.writeValueAsString(map));out.flush();out.close();}}).permitAll() // 设置退出登录的所有权限.and() // 获取一个安全编译器.csrf().disable() // 关闭csrf跨站点请求伪造.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint());// 自定义认证的入口异常处理方法http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);// 重写用户名密码的过滤器,实现前后端分离获取登录的用户名,密码信息http.exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());// 没有权限访问的处理器}}
3.3.1CustomAccessDeniedHandler自定义没权限方法的处理器
package com.lz.hehuorenservice.common.bean;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.web.access.AccessDeniedHandler;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.util.HashMap;import java.util.Map;/** Create by hyhweb on 2021/6/7 11:50 */public class CustomAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,AccessDeniedException e)throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=utf-8");PrintWriter out = httpServletResponse.getWriter();Map map = new HashMap<>();map.put("message", "权限不足,请联系管理员开通权限");map.put("code", 0);map.put("status", 403);map.put("success", false);String result = new ObjectMapper().writeValueAsString(map);out.write(result);out.flush();out.close();}}
3.3.2CustomAuthenticationEntryPoint自定义认证的入口
package com.lz.hehuorenservice.common.bean;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.AuthenticationEntryPoint;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.util.HashMap;import java.util.Map;/** Create by hyhweb on 2021/6/7 11:42 */public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,AuthenticationException e)throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=utf-8");PrintWriter out = httpServletResponse.getWriter();Map map = new HashMap<>();map.put("message", "还没登录,请重新登录");map.put("code", 302);String result = new ObjectMapper().writeValueAsString(map);out.write(result);out.flush();out.close();}}
3.3.3.CustomAuthenticationFilter自定义
package com.lz.hehuorenservice.common.filter;import org.springframework.http.MediaType;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.InputStream;/** Create by hyhweb on 2021/6/7 12:07 */public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {UsernamePasswordAuthenticationToken authRequest = null;try (InputStream is = request.getInputStream()) {ObjectMapper mapper = new ObjectMapper();Map<String, String> authenticationBean = mapper.readValue(is, Map.class);authRequest = new UsernamePasswordAuthenticationToken(authenticationBean.get("userName"), authenticationBean.get("userPassWord"));/* authRequest =new UsernamePasswordAuthenticationToken(request.getParameter("userName"), request.getParameter("userPassWord"));*/} catch (IOException e) {e.printStackTrace();authRequest = new UsernamePasswordAuthenticationToken("", "");} finally {setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}} else {return super.attemptAuthentication(request, response);}}}
4.controller层使用权限注释@PreAuthorize实现权限控制
@RestController@RequestMapping("/user")@Api(tags = "用户信息")public class UserController{@Autowired private UserService userService;@ApiOperation(value = "删除单个对象", notes = "删除单个对象接口")@GetMapping("/delete/{id}")@PreAuthorize("hasAuthority('delete')")public ApiResult deleteById(@PathVariable long id) {return userService.deleteById(id);}}
附加说明:
Spring Security的表达式对象的基类:
org.springframework.security.access.expression.SecurityExpressionRoot
在controller的方法中使用注释,如下:
@PreAuthorize("表达式('权限值')")
@PreAuthorize("hasAuthority('zixunguanli-xinzeng')")public ApiResult add(@RequestBody String json) {return infoService.add(JSON.parseObject(json, InfoReq.class));}
表达式如下:
boolean hasAuthority(String var1);boolean hasAnyAuthority(String... var1);boolean hasRole(String var1);boolean hasAnyRole(String... var1);boolean permitAll();boolean denyAll();boolean isAnonymous();boolean isAuthenticated();boolean isRememberMe();boolean isFullyAuthenticated();boolean hasPermission(Object var1, Object var2);boolean hasPermission(Object var1, String var2, Object var3);
Spring Security的重构获取用户名和密码的方式,实现前后端分离的json格式,如下:
重构
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter的attemptAuthentication方法