spring security

spring security 简单做一下 

第一步

引个包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>

只要引入个包 只要你访问接口 他自动回跳转到登录页面

默认用户名是user 密码会打印到控制台里

但我们显然不会用这种方式 一般都是用数据库

那好了 接下来

第二步

package com.example.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.annotation.Resource;
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;

@Configuration
//优先级是第一位的
public class SecurityConfig extends WebSecurityConfigurerAdapter {
     //这个service是自己写的 但是UserDetailsService这个接口确实框架自己的 只不过需要实现它
    @Resource(name = "ss")
    private UserDetailsService userDetailService;

    //这个必须要 因为比如说下面这个 他会选择不把登录填的密码加密与实际密码比对 如果是其他方式会加密
    //密码比对是有这个框架自动做的 (至少我现在写的这种方式是)
    @Bean
    PasswordEncoder passwordEncoder(){
        return  NoOpPasswordEncoder.getInstance();
    }
    //尤其注意这个 AuthenticationManagerBuilder  因为父类重构了好几个configure方法认准这个就是做认证的
    //就是下面这个.anyRequest().authenticated() 会来这里认证
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     auth.userDetailsService(userDetailService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()
                //这个很明显除了这个url不需要认证 其他都需要
                .antMatchers("/uua/fegin/**").permitAll()
               //需要加这个ROLE这个前缀因为他自动有
                .antMatchers("/uua/fegin1/**").hasRole("admin")
                .anyRequest().authenticated()               //所有其他的URL都需要用户进行验证
                .and()
                //表单认证 这个就是一开始咱们只引包默认的那个登录页面 不写这个 那就没有这个页面 只会返回403
                .formLogin()
                //登录成功怎样 当然也有登录失败怎样
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        Map map = new HashMap();
                        map.put("msg","登录成功");
                        ObjectMapper om = new ObjectMapper();
                        PrintWriter writer = httpServletResponse.getWriter();
                        writer.write("登录成功");
                        writer.flush();
                        writer.close();
                    }
                })
                .and().csrf().disable();
        //这个先注释掉是因为默认他的登录信息是保存在session里 我们不想用的会就得有下面这两句 比如保存在redis里了或这个JWTtoken
//                .sessionManagement()
//                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //这个他的默认登录信息是保存在session里
    }


}

第三步

就是我刚才写的userDetailService的实现类 还有User也必须实现UserDetail

package com.example.demo;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class User  implements UserDetails {
    private  String username;
    private  String password;
    private  Boolean enabled;
    private  Boolean locked;
    private  List<Role> roles;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        roles = new ArrayList<>();
        Role role1 = new Role();
        //需要加ROLE这个前缀 因为自动加有
        role1.setName("ROLE_ADMIN");
        roles.add(role1);
        for(Role role : roles){
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Boolean getEnabled() {
        return enabled;
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public Boolean getLocked() {
        return locked;
    }

    public void setLocked(Boolean locked) {
        this.locked = locked;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
}

完毕 接下来大概说一下原理

首先访问 一个受限的资源

http://10.0.75.1:8083/uua/fegin1/test

 

 

spring security 默认情况下是会把他的登录信息 保存在session 每次去 请求需要授权的资源都会去session里取 清除浏览器的session 登录信息会失效

关键代码 

首选就去session 取看有没有保存登录信息

HttpSessionSecurityContextRepository.class  
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
           
          //其他部分省略
         SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
           

        }
    }
HttpSessionSecurityContextRepository.class方法里
  private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
        boolean debug = this.logger.isDebugEnabled();
        if (httpSession == null) {
            if (debug) {
                this.logger.debug("No HttpSession currently exists");
            }

            return null;
        } else {
            Object contextFromSession = httpSession.getAttribute(this.springSecurityContextKey);
            if (contextFromSession == null) {
                if (debug) {
                    this.logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
                }

                return null;
            } else if (!(contextFromSession instanceof SecurityContext)) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn(this.springSecurityContextKey + " did not contain a SecurityContext but contained: '" + contextFromSession + "'; are you improperly modifying the HttpSession directly (you should always use SecurityContextHolder) or using the HttpSession attribute reserved for this class?");
                }

                return null;
            } else {
                if (debug) {
                    this.logger.debug("Obtained a valid SecurityContext from " + this.springSecurityContextKey + ": '" + contextFromSession + "'");
                }

                return (SecurityContext)contextFromSession;
            }
        }
    }

2.如果有session保存有登录信息 就把登录信息保存到 

 
SecurityContextHolder.class
 public static void setContext(SecurityContext context) {
        strategy.setContext(context);
    }

现在就是没有登录

实际保存到了

ThreadLocalSecurityContextHolderStrategy.class 
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal();就这里

下面就是重头戏 主要就是

AbstractSecurityInterceptor.class
beforeInvocation 这个方法
  protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        boolean debug = this.logger.isDebugEnabled();
        if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
            throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + this.getSecureObjectClass());
        } else {

//这个就是我当前访问的链接应该需要什么角色才能访问
            Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
            if (attributes != null && !attributes.isEmpty()) {
                if (debug) {
                    this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
                }

                if (SecurityContextHolder.getContext().getAuthentication() == null) {
                    this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
                }
               //这个就会把刚才往session取得信息去出来 没有也没关系他会自动创建个匿名用户
                Authentication authenticated = this.authenticateIfRequired();

                try {
//校验权限
                    this.accessDecisionManager.decide(authenticated, object, attributes);
                } catch (AccessDeniedException var7) {
                    this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));
                    throw var7;
                }

                if (debug) {
                    this.logger.debug("Authorization successful");
                }

                if (this.publishAuthorizationSuccess) {
                    this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
                }

                Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
                if (runAs == null) {
                    if (debug) {
                        this.logger.debug("RunAsManager did not change Authentication object");
                    }

                    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
                } else {
                    if (debug) {
                        this.logger.debug("Switching to RunAs Authentication: " + runAs);
                    }

                    SecurityContext origCtx = SecurityContextHolder.getContext();
                    SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
                    SecurityContextHolder.getContext().setAuthentication(runAs);
                    return new InterceptorStatusToken(origCtx, true, attributes, object);
                }
            } else if (this.rejectPublicInvocations) {
                throw new IllegalArgumentException("Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. This indicates a configuration error because the rejectPublicInvocations property is set to 'true'");
            } else {
                if (debug) {
                    this.logger.debug("Public object - authentication not attempted");
                }

                this.publishEvent(new PublicInvocationEvent(object));
                return null;
            }
        }
    }

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值