权限控制基本上是任何一个web项目都要有的,为此spring为我们提供security模块来实现权限控制,网上找了很多资料,但是提供的demo代码都不能完全满足我的需求,因此自己整理了一版。
在上代码之前,大家需要理解两个过程:认证和授权
用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。
访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。
整合步骤如下:
1、引入依赖和添加mybatis generator插件
4.0.0
powerx.io
springboot-security
0.0.1-SNAPSHOT
jar
springboot-security
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.0.5.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
mysql
mysql-connector-java
runtime
com.github.pagehelper
pagehelper-spring-boot-starter
1.2.5
com.alibaba
druid-spring-boot-starter
1.1.9
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.2
src/main/resources/generator/generatorConfig.xml
true
true
2、建立对应的表,标准的基于角色权限控制的五张表,建表语句我也放到代码中了。
3、利用逆向工程生成对应的model、mapper和映射文件等
4、spring security配置,关键位置我都加了注释
WebSecurityConfig.java
packagecom.example.demo.config;importjava.io.IOException;importjava.io.PrintWriter;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.authentication.BadCredentialsException;importorg.springframework.security.config.annotation.ObjectPostProcessor;importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.builders.WebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.security.web.access.intercept.FilterSecurityInterceptor;importorg.springframework.security.web.authentication.AuthenticationFailureHandler;importorg.springframework.security.web.authentication.AuthenticationSuccessHandler;importcom.example.demo.service.UserService;
@Configurationpublic class WebSecurityConfig extendsWebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Autowired
MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
@Autowired
MyAccessDecisionManager myAccessDecisionManager;
@Autowired
AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;/*** 自定义的加密算法
*@return
*/@BeanpublicPasswordEncoder myPasswordEncoder() {return newMyPasswordEncoder();
}
@Overrideprotected void configure(AuthenticationManagerBuilder auth) throwsException {
auth.userDetailsService(userService).passwordEncoder(myPasswordEncoder());
}
@Overridepublic void configure(WebSecurity web) throwsException {
web.ignoring().antMatchers("/index.html", "/static/**","/loginPage","/register");
}
@Overrideprotected void configure(HttpSecurity http) throwsException {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor() {
@Overridepublic O postProcess(O o) {
o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(myAccessDecisionManager);returno;
}
}).and().formLogin().loginPage("/loginPage").loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password").permitAll().failureHandler(newAuthenticationFailureHandler() {
@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throwsIOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out=httpServletResponse.getWriter();
StringBuffer sb= newStringBuffer();
sb.append("{\"status\":\"error\",\"msg\":\"");if (e instanceof UsernameNotFoundException || e instanceofBadCredentialsException) {
sb.append("用户名或密码输入错误,登录失败!");
}else{
sb.append("登录失败!");
}
sb.append("\"}");
out.write(sb.toString());
out.flush();
out.close();
}
}).successHandler(newAuthenticationSuccessHandler() {
@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throwsIOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out=httpServletResponse.getWriter();
String s= "{\"status\":\"success\",\"msg\":\"登陆成功\"}";
out.write(s);
out.flush();
out.close();
}
}).and().logout().permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(authenticationAccessDeniedHandler);
}
}
MyFilterInvocationSecurityMetadataSource.java
packagecom.example.demo.config;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.access.ConfigAttribute;importorg.springframework.security.access.SecurityConfig;importorg.springframework.security.web.FilterInvocation;importorg.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;importorg.springframework.security.web.util.matcher.AntPathRequestMatcher;importorg.springframework.stereotype.Service;importcom.example.demo.dao.PermissionMapper;importcom.example.demo.model.Permission;importjavax.servlet.http.HttpServletRequest;import java.util.*;importjava.util.Map.Entry;
@Servicepublic class MyFilterInvocationSecurityMetadataSource implementsFilterInvocationSecurityMetadataSource {
@AutowiredprivatePermissionMapper permissionMapper;private HashMap> map = null;/*** 加载权限表中所有权限*/
public voidloadResourceDefine() {
map= new HashMap>();
List permissions =permissionMapper.findAll();for(Permission permission : permissions) {
ConfigAttribute cfg= newSecurityConfig(permission.getPermissionname());
List list = new ArrayList<>();
list.add(cfg);
map.put(permission.getUrl(), list);
}
}/*** 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法, 用来判定用户
* 是否有此权限。如果不在权限表中则放行。*/@Overridepublic Collection getAttributes(Object object) throwsIllegalArgumentException {if (map == null) {
loadResourceDefine();
}//object 中包含用户请求的request的信息
HttpServletRequest request =((FilterInvocation) object).getHttpRequest();for (Entry>entry : map.entrySet()) {
String url=entry.getKey();if (newAntPathRequestMatcher(url).matches(request)) {returnmap.get(url);
}
}return null;
}
@Overridepublic CollectiongetAllConfigAttributes() {return null;
}
@Overridepublic boolean supports(Class>clazz) {return true;
}
}
MyAccessDecisionManager.java
packagecom.example.demo.config;importorg.springframework.security.access.AccessDecisionManager;importorg.springframework.security.access.AccessDeniedException;importorg.springframework.security.access.ConfigAttribute;importorg.springframework.security.authentication.InsufficientAuthenticationException;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.stereotype.Service;importjava.util.Collection;importjava.util.Iterator;
@Servicepublic class MyAccessDecisionManager implementsAccessDecisionManager {/*** decide 方法是判定是否拥有权限的决策方法,authentication是CustomUserService
* 中循环添加到 GrantedAuthority 对象中的权限信息集合,object 包含客户端发起的请求的requset信息,
* 可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
* configAttributes为MyFilterInvocationSecurityMetadataSource的getAttributes(Object object)
* 这个方法返回的结果.
**/@Overridepublic void decide(Authentication authentication, Object object, Collection configAttributes) throwsAccessDeniedException, InsufficientAuthenticationException {if(null== configAttributes || configAttributes.size() <=0) {return;
}
ConfigAttribute c;
String needRole;for(Iterator iter =configAttributes.iterator(); iter.hasNext(); ) {
c=iter.next();
needRole=c.getAttribute();for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
if(needRole.trim().equals(ga.getAuthority())) {return;
}
}
}throw new AccessDeniedException("no right");
}
@Overridepublic booleansupports(ConfigAttribute attribute) {return true;
}
@Overridepublic boolean supports(Class>clazz) {return true;
}
}
AuthenticationAccessDeniedHandler.java
packagecom.example.demo.config;importorg.springframework.security.access.AccessDeniedException;importorg.springframework.security.web.access.AccessDeniedHandler;importorg.springframework.stereotype.Component;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.io.PrintWriter;
@Componentpublic class AuthenticationAccessDeniedHandler implementsAccessDeniedHandler {
@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp, AccessDeniedException e) throwsIOException, ServletException {
resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
resp.setContentType("application/json;charset=UTF-8");
PrintWriter out=resp.getWriter();
out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
out.flush();
out.close();
}
}
MyPasswordEncoder.java
packagecom.example.demo.config;importorg.springframework.security.crypto.password.PasswordEncoder;public class MyPasswordEncoder implementsPasswordEncoder {
@OverridepublicString encode(CharSequence charSequence) {returncharSequence.toString();
}
@Overridepublic booleanmatches(CharSequence charSequence, String s) {returns.equals(charSequence.toString());
}
}
UserServiceImpl.java
packagecom.example.demo.service.impl;importjava.util.ArrayList;importjava.util.List;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importcom.example.demo.dao.PermissionMapper;importcom.example.demo.dao.RoleMapper;importcom.example.demo.dao.UserMapper;importcom.example.demo.model.Permission;importcom.example.demo.model.User;importcom.example.demo.service.UserService;
@Servicepublic class UserServiceImpl implementsUserService {
@AutowiredprivatePermissionMapper permissionMapper;
@AutowiredprivateRoleMapper roleMapper;
@AutowiredprivateUserMapper userMapper;
@AutowiredprivatePasswordEncoder passwordEncoder;
@Overridepublic UserDetails loadUserByUsername(String username) throwsUsernameNotFoundException {
User user=userMapper.selectByUsername(username);if (user != null) {
List permissions =permissionMapper.findByUserId(user.getId());
List grantedAuthorities = new ArrayList <>();for(Permission permission : permissions) {if (permission != null && permission.getPermissionname()!=null) {
GrantedAuthority grantedAuthority= newSimpleGrantedAuthority(permission.getPermissionname());
grantedAuthorities.add(grantedAuthority);
}
}return neworg.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities);
}else{throw new UsernameNotFoundException("username: " + username + " do not exist!");
}
}
@Transactional
@Overridepublic voiduserRegister(String username, String password) {
User user= newUser();
user.setUsername(passwordEncoder.encode(username));
user.setPassword(password);
userMapper.insert(user);
User rtnUser=userMapper.selectByUsername(username);//注册成功默认给用户的角色是user
roleMapper.insertUserRole(rtnUser.getId(), 2);
}
}
至此,整合基本完毕,其它控制层的代码和mapper层的代码不再贴出,需要注意的是注册用户的时候我们要用自定义的加密工具对密码进行加密(当然在demo中我什么也没做),其它的一些功能比如给用户加角色、给角色加权限等的增删改查,大家可以根据需要自行添加,另外在permissionMapper.findByUserId(user.getId())这里我写了一个五张表的关联查询,可以根据userid可以查出用户所有对应的权限。