(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;
    }
}

至此自定义的用户权限认证和授权已经完美实现。
希望对大家有帮助

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

希克

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值