首先添加spring security所用到的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
开始配置 spring security
1、 配置权限
通过继承WebSecurityConfigurerAdapter,我们可以在这里配置security的功能,例如加决策器,拦截器之类的等等。这里注意跳转的登录页面中表单所提交的用户名和密码所使用的字段是username和password,表单提交的url为/user/login,method为post。
package per.san.common.config;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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 org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import per.san.common.security.service.CustomUserDetailsService;
import per.san.sys.domain.SysPermission;
import per.san.sys.mapper.SysPermissionMapper;
import java.util.List;
/**
* description:
*
* @author shencai.huang@hand-china.com
* @date 12/29/2018 17:14
* lastUpdateBy: shencai.huang@hand-china.com
* lastUpdateDate: 12/29/2018
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法级的权限认证
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SysPermissionMapper sysPermissionMapper;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
@Bean
UserDetailsService customUserService() { //注册UserDetailsService 的bean
return new CustomUserDetailsService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService()); //user Details Service验证
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
List<SysPermission> permissionList = sysPermissionMapper.selectAllPublicPermission();
List<String> urls = Lists.newArrayList("/public/**", "/login", "/register", "/find", "/swagger-ui.html",
"/swagger-resources/**", "/images/**", "/webjars/**", "/v2/api-docs", "/configuration/ui",
"/configuration/security");
permissionList.forEach(item -> urls.add(item.getPath()));
String[] antMatchers = new String[urls.size()];
for (int i = 0; i < antMatchers.length; i++) {
antMatchers[i] = urls.get(i);
}
http.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers(antMatchers)
.permitAll() // 设置所有人都可以访问
// .anyRequest() // 任何请求,登录后可以访问
// .permitAll()
.and()
// 设置登陆页
.formLogin().loginPage("/login")
.loginProcessingUrl("/user/login")
// 设置登陆成功页
.defaultSuccessUrl("/").permitAll()
.and()
.logout().permitAll()
.and()
.csrf().disable(); // 关闭csrf防护
}
}
2、自定义CustomUserDetailsService继承UserDetailService
UserDetailService是spring security提供给我们的获取用户信息的Service,主要给security提供验证用户的信息,这里我们就可以自定义自己的需求了,我这个就是根据username从数据库获取该用户的信息,然后交给security进行后续比对和处理。
package per.san.common.security.service;
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 per.san.sys.domain.SysPermission;
import per.san.sys.domain.SysUser;
import per.san.sys.mapper.SysPermissionMapper;
import per.san.sys.mapper.SysUserMapper;
import java.util.ArrayList;
import java.util.List;
/**
* description:
*
* @author Sanchar
* @date 2/16/2019 17:15
* lastUpdateBy: Sanchar
* lastUpdateDate: 2/16/2019 17:15
*/
@Component
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysPermissionMapper sysPermissionMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = sysUserMapper.selectByUsername(username);
if (sysUser != null) {
sysUser = sysUserMapper.selectByPrimaryKey(sysUser.getId());
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
List<SysPermission> permissionList;
if (sysUser.getIsAdmin()) {
permissionList = sysPermissionMapper.selectAllProtectPermission();
permissionList.forEach(item -> grantedAuthorities.add(new SimpleGrantedAuthority(item.getCode())));
} else {
permissionList = sysPermissionMapper.selectPermissionByUserId(sysUser.getId());
permissionList.forEach(item -> grantedAuthorities.add(new SimpleGrantedAuthority(item.getCode())));
}
return new User(sysUser.getUserName(), sysUser.getPassword(), grantedAuthorities);
} else {
throw new UsernameNotFoundException("user: " + username + " do not exist!");
}
}
}
3、 新增自定义CustomAccessDecisionManager
package per.san.common.security.service;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
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;
import java.util.Collection;
import java.util.Iterator;
/**
* description:
*
* @author Sanchar
* @date 2/19/2019 20:51
* lastUpdateBy: Sanchar
* lastUpdateDate: 2/19/2019 20:51
*/
@Service
public class CustomAccessDecisionManager implements AccessDecisionManager {
// decide 方法是判定是否拥有权限的决策方法,
//authentication 是释CustomUserDetailsService中循环添加到 GrantedAuthority 对象中的权限信息集合.
//object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
//configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if (null == configAttributes || configAttributes.size() <= 0) {
return;
}
ConfigAttribute c;
String needRole;
for (Iterator<ConfigAttribute> iterator = configAttributes.iterator(); iterator.hasNext(); ) {
c = iterator.next();
needRole = c.getAttribute();
for (GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
if (needRole.trim().equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("no right");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
4、新增自定义CustomFilterSecurityInterceptor
package per.san.common.security.service;
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;
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 java.io.IOException;
/**
* description:
*
* @author Sanchar
* @date 2/19/2019 20:54
* lastUpdateBy: Sanchar
* lastUpdateDate: 2/19/2019 20:54
*/
@Service
public class CustomFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(CustomAccessDecisionManager customAccessDecisionManager) {
super.setAccessDecisionManager(customAccessDecisionManager);
}
@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;
}
}
5、新增自定义CustomInvocationSecurityMetadataSourceService
package per.san.common.security.service;
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 per.san.sys.domain.SysPermission;
import per.san.sys.mapper.SysPermissionMapper;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* description:
*
* @author Sanchar
* @date 2/19/2019 20:56
* lastUpdateBy: Sanchar
* lastUpdateDate: 2/19/2019 20:56
*/
@Service
public class CustomInvocationSecurityMetadataSourceService implements
FilterInvocationSecurityMetadataSource {
@Autowired
private SysPermissionMapper sysPermissionMapper;
private HashMap<String, Collection<ConfigAttribute>> map = null;
private HashMap<String, SysPermission> permissionHashMap = null;
/**
* 加载权限表中所有权限
*/
public void loadResourceDefine(){
map = new HashMap<>();
permissionHashMap = new HashMap<>();
Collection<ConfigAttribute> array;
ConfigAttribute cfg;
List<SysPermission> permissionList = sysPermissionMapper.selectAllProtectPermission();
for(SysPermission permission : permissionList) {
array = new ArrayList<>();
cfg = new SecurityConfig(permission.getCode());
//此处只添加了用户的名字,其实还可以添加更多权限的信息,例如请求方法到ConfigAttribute的集合中去。此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。
array.add(cfg);
//用权限的getUrl() 作为map的key,用ConfigAttribute的集合作为 value,
map.put(permission.getCode(), array);
permissionHashMap.put(permission.getCode(), permission);
}
}
//此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
if(map == null || permissionHashMap == null) loadResourceDefine();
//object 中包含用户请求的request 信息
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
AntPathRequestMatcher matcher;
String code;
for(Iterator<String> iterator = map.keySet().iterator(); iterator.hasNext(); ) {
code = iterator.next();
SysPermission permission = permissionHashMap.get(code);
matcher = new AntPathRequestMatcher(permission.getPath(), permission.getMethod().toUpperCase());
if(matcher.matches(request)) {
return map.get(code);
}
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}