(Spring Security)实战干货一,自定义权鉴的实现
引言
SSM(Spring+SpringMVC+MyBatis)整合SpringSecurity实现权限认证和授权。以下内容完全基于你已经了解属性SSM基础上完成。废话不多说,直接上源码。
配置
Maven工程pom.xml包导入设置
截取security相关配置部分
<properties>
.....
<spring.security.version>5.0.1.RELEASE</spring.security.version>
</properties>
<dependencies>
.....
<!--security权限认证-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--thymeleaf与security 配合-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
</dependencies>
spring-mvc配置
spring-mcv.xml与security相关部分配置
<bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
<!--thymeleaf配合security部分-->
<property name="additionalDialects">
<set>
<bean class="org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect"></bean>
</set>
</property>
</bean>
spring-security配置
最为核心重要的配置,该配置,完全是基于动态连接数据库实现
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- 配置不过滤的资源(静态资源及登录相关) -->
<security:http security="none" pattern="/static/**" />
<security:http security="none" pattern="/images/**" />
<security:http security="none" pattern="/js/**" />
<security:http security="none" pattern="/style/**" />
<security:http security="none" pattern="/favicon.ico" />
<!--操作界面-->
<security:http security="none" pattern="/login.jsp" />
<!--auto-config:默认登录框,use-expressions:SPEL表达式-->
<security:http auto-config="false" use-expressions="false" >
<security:form-login
login-page="/login.jsp"
login-processing-url="/"
username-parameter="username"
password-parameter="password"
default-target-url="/index"
always-use-default-target="true"
/>
<!--自定义过滤器(实现权鉴可在此处实现)-->
<security:custom-filter ref="oursFilterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>
<!-- 关闭CSRF(跨域)true,false默认是开启的 -->
<security:csrf disabled="true" />
<!--停用对匿名认证的支持-->
<security:anonymous enabled="false"/>
<!--iframe调用-->
<security:headers>
<security:frame-options policy="SAMEORIGIN"/>
</security:headers>
<!--退出-->
<security:logout invalidate-session="true" logout-url="/logout" logout-success-url="/common/login"></security:logout>
<!--403页面-->
<!--<security:access-denied-handler error-page="/403"></security:access-denied-handler>-->
</security:http>
<!-- 动态:切换成数据库中的用户名和密码 -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="oursUserDetailsService">
<!-- 配置加密的方式-->
<security:password-encoder ref="oursPasswordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!-- 配置加密类(已经通过注解方式添加bean了!此处注释掉) -->
<!--<bean id="oursPasswordEncoder" class="cc.eeec.com.libs.security.OursPasswordEncoder"/>-->
<!-- 认证过滤器 -->
<bean id="oursFilterSecurityInterceptor"
class="index.security.OursFilterSecurityInterceptor">
<property name="rejectPublicInvocations" value="false"/>
<!-- 用户拥有的权限 -->
<property name="accessDecisionManager" ref="accessDecisionManager" />
<!-- 用户是否拥有所请求资源的权限 -->
<property name="authenticationManager" ref="authenticationManager" />
<!-- 资源与权限对应关系 -->
<property name="securityMetadataSource" ref="securityMetadataSource" />
</bean>
<!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<bean id="accessDecisionManager" class="index.security.OursAccessDecisionManager"></bean>
<!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 -->
<bean id="securityMetadataSource" class="index.security.OursSecurityMetadataSource" ></bean>
</beans>
核心文件
略去包名和import引入
OursFilterSecurityInterceptor.class
public class OursFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
/*public OursFilterSecurityInterceptor(){
System.out.println("安全拦截器1:OursFilterSecurityInterceptor");
}*/
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
private FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = true;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//System.out.println("安全拦截器1.1");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//System.out.println("安全拦截器1.2");
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
@Override
public void destroy() {
//System.out.println("安全拦截器1.3");
}
@Override
public Class<? extends Object> getSecureObjectClass() {
//System.out.println("安全拦截器1.4");
return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//System.out.println("安全拦截器1.5");
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
// System.out.println("安全拦截器1.6");
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(
FilterInvocationSecurityMetadataSource newSource) {
//System.out.println("安全拦截器1.7");
this.securityMetadataSource = newSource;
}
}
OursAccessDecisionManager.class
/**
* 自定义访问决策器
*/
public class OursAccessDecisionManager implements AccessDecisionManager {
/*public OursAccessDecisionManager(){
System.out.println("访问决策管理器2:OursAccessDecisionManager");
}*/
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
//System.out.println("访问决策管理器2.1:验证用户是否具有一定的权限");
if (collection == null) {
return;
}
Iterator<ConfigAttribute> it = collection.iterator();
while (it.hasNext()) {
String needResource = it.next().getAttribute();
Collection<? extends GrantedAuthority> authorities=authentication.getAuthorities();
for (GrantedAuthority ga : authorities) {
if (needResource.equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("没有访问权限!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
//System.out.println("访问决策管理器2.2");
return true;
}
@Override
public boolean supports(Class<?> aClass) {
//System.out.println("访问决策管理器2.3");
return true;
}
}
OursSecurityMetadataSource.class
/**
* 过滤器:资源数据定义。
* 将全部的资源和权限相应关系建立起来,
* 即定义某一资源能够被哪些角色访问
*/
//@Component("oursSecurityMetadataSource")
public class OursSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
/*public OursSecurityMetadataSource() {
System.out.println("安全元数据3:OursSecurityMetadataSource");
}*/
@Autowired
private IRoleService roleService;
@Autowired
private IAuthAccessService authAccessService;
private AntPathMatcher urlMatcher = new AntPathMatcher();
//保存资源和权限的相应关系 key(资源url) value(权限)
private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
private void loadResourcesDefine() throws Exception {
//System.out.println("安全元数据3.1:开始载入资源列表数据");
resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
List<AuthAccess> authAccesses = authAccessService.findAll();
Collection<ConfigAttribute> configAttributes;
for (AuthAccess authAccess : authAccesses) {
//根据资源列表获取对于的role信息
Role role = roleService.findById(authAccess.getRole_id());
ConfigAttribute configAttribute = new SecurityConfig(role.getCode());
//
if (resourceMap.containsKey(authAccess.getRule_name())) {
configAttributes = resourceMap.get(authAccess.getRule_name());
configAttributes.add(configAttribute);
} else {
configAttributes = new ArrayList<ConfigAttribute>();
configAttributes.add(configAttribute);
resourceMap.put(authAccess.getRule_name(), configAttributes);
}
}
//--展示权限对应关系--
/*Set<String> set = resourceMap.keySet();
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println("key:" + s + ",value:" + resourceMap.get(s));
}*/
//--
}
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
try {
loadResourcesDefine();
} catch (Exception e) {
e.printStackTrace();
}
//System.out.println("安全元数据3.2");
//避免返回null值,否则AccessDecisionManager失效
Collection<ConfigAttribute> collection = new LinkedList<>();
//o 是一个URL?
String url = ((FilterInvocation) o).getRequestUrl();
System.out.println("请求地址为:" + url);
//--
Iterator<String> it = resourceMap.keySet().iterator();
while (it.hasNext()) {
String resUrl = it.next();
//
if (resUrl.indexOf("?") != -1) {
resUrl = resUrl.substring(0, resUrl.indexOf("?"));
}
if (urlMatcher.match(resUrl, url)) {
System.out.println("须要的权限是:" + resourceMap.get(resUrl));
return resourceMap.get(resUrl);
}
}
// 坑:大多数网上提供的返回值是null,如果是null则会跳过不执行自定义的OursAccessDecisionManager访问决策管理器,自定义的权鉴将无效
collection.add(new SecurityConfig("ROLE_NO_USER"));
return collection;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
//System.out.println("安全元数据3.3");
return null;
}
@Override
public boolean supports(Class<?> aClass) {
//System.out.println("安全元数据3.4");
return true;
}
}
OursPasswordEncoder.class
/**
* 编码器:自定义密码验证编码器
*/
@Component("oursPasswordEncoder")
public class OursPasswordEncoder implements PasswordEncoder {
/*public OursPasswordEncoder(){
System.out.println("密码编码器4:OursPasswordEncoder");
}*/
/**
* 加密
*
* @param charSequence
* @return
*/
@Override
public String encode(CharSequence charSequence) {
//System.out.println("密码编码器4.1");
//自定义盐
String authCode = null;
String AUTHCODE = "盐";//
if (!(authCode != null && authCode.length() != 0)) {
authCode = AUTHCODE;
}
//加密方式 简单MD5加密,可以用securit自带加密方式,更安全,此处是演示使用,自定义自己的加密方式
String password = (String) charSequence;
password = authCode + password;
password = DigestUtils.md5DigestAsHex(password.getBytes());
return password;
}
/**
* 加密对比
*
* @param charSequence
* @param s
* @return
*/
@Override
public boolean matches(CharSequence charSequence, String s) {
//System.out.println("密码编码器4.2");
//加密部分
String password = encode(charSequence);
//--
return s.equals(password);
}
}
OursUserDetailsService.class
/**
* 服务器:自定义,用户认证服务
*/
@Component("oursUserDetailsService")
public class OursUserDetailsService implements UserDetailsService {
public OursUserDetailsService(){
System.out.println("用户详情5:OursUserDetailsService");
}
@Autowired
private IUsersService usersService;
/**
* Security权限管理用户数据库方法
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("用户详情5.1");
//查询用户
Users users = usersService.findByUsername(username);
if (users == null) {
throw new UsernameNotFoundException("用户不存在");
}
//方式三:貌似还是有问题
List<String> roles = usersService.findRolesCodeByUserId(users.getId());
List<String> rules = usersService.findRuleNameByUserId(users.getId());
List<String> authorities = new ArrayList<>();
if (users.getId() == 1) {
authorities.add("ROLE_ADMIN");
} else {
roles = roles.stream()
.map(rc -> "ROLE_" + rc)
.collect(Collectors.toList());
authorities.addAll(roles);
authorities.addAll(rules);
}
User user = new User(users.getUsername(), users.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", authorities)));
return user;
}
}
至此自定义的用户权限认证和授权已经完美实现。
希望对大家有帮助