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