spring security 原理+实战
疯狂创客圈 Java 高并发【 亿级流量聊天室实战】实战系列 【博客园总入口 】
架构师成长+面试必备之 高并发基础书籍 【Netty Zookeeper Redis 高并发实战 】
前言
Crazy-SpringCloud 微服务脚手架 &视频介绍:
Crazy-SpringCloud 微服务脚手架,是为 Java 微服务开发 入门者 准备的 学习和开发脚手架。并配有一系列的使用教程和视频,大致如下:
高并发 环境搭建 图文教程和演示视频,陆续上线:
| 中间件 | 链接地址 | 
|---|---|
| Linux Redis 安装(带视频) | Linux Redis 安装(带视频) | 
| Linux Zookeeper 安装(带视频) | Linux Zookeeper 安装, 带视频 | 
| Windows Redis 安装(带视频) | Windows Redis 安装(带视频) | 
| RabbitMQ 离线安装(带视频) | RabbitMQ 离线安装(带视频) | 
| ElasticSearch 安装, 带视频 | ElasticSearch 安装, 带视频 | 
| Nacos 安装(带视频) | Nacos 安装(带视频) | 
Crazy-SpringCloud 微服务脚手架 图文教程和演示视频,陆续上线:
| 组件 | 链接地址 | 
|---|---|
| Eureka | Eureka 入门,带视频 | 
| SpringCloud Config | springcloud Config 入门,带视频 | 
| spring security | spring security 原理+实战 | 
| Spring Session | SpringSession 独立使用 | 
| 分布式 session 基础 | RedisSession (自定义) | 
| 重点: springcloud 开发脚手架 | springcloud 开发脚手架 | 
| SpingSecurity + SpringSession 死磕 (写作中) | SpingSecurity + SpringSession 死磕 | 
小视频以及所需工具的百度网盘链接,请参见 疯狂创客圈 高并发社群 博客
Spring Security 的重要性
在web应用开发中,安全无疑是十分重要的,选择Spring Security来保护web应用是一个非常好的选择。Spring Security 是spring项目之中的一个安全模块,特别是在spring boot项目中,spring security已经默认集成和启动了。
Spring Security 默认为自动开启的,可见其重要性。
如果要关闭,需要在启动类加上,exclude ={SecurityAutoConfiguration} 的配置
@EnableEurekaClient
@SpringBootApplication(scanBasePackages = {
        "com.crazymaker.springcloud.user",
        "com.crazymaker.springcloud.seckill.remote.fallback",
        "com.crazymaker.springcloud.standard"
}, exclude = {SecurityAutoConfiguration.class})
或者
spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
一般不建议关闭。
Spring Security 核心组件
spring security核心组件有:Userdetails 、Authentication,UserDetailsService、AuthenticationProvider、AuthenticationManager 下面分别介绍。
Authentication
authentication 直译过来是“认证”的意思,在Spring Security 中Authentication用来表示当前用户是谁,一般来讲你可以理解为authentication就是一组用户名密码信息。Authentication也是一个接口,其定义如下:
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
接口有4个get方法,分别获取
- Authorities, 填充的是用户角色信息。 
- Credentials,直译,证书。填充的是密码。 
- Details ,用户信息。 
- Principal 直译,形容词是“主要的,最重要的”,名词是“负责人,资本,本金”。感觉很别扭,所以,还是不翻译了,直接用原词principal来表示这个概念,其填充的是用户名。 
 因此可以推断其实现类有这4个属性。
这几个方法作用如下:
- getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。 
- getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。 
- getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息) 
- getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails (UserDetails也是一个接口,里边的方法有getUsername,getPassword等)。 
- isAuthenticated: 获取当前 Authentication 是否已认证。 
- setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。 
UserDetails
UserDetails,看命知义,是用户信息的意思。其存储的就是用户信息,其定义如下:
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
方法含义如下:
- getAuthorites:获取用户权限,本质上是用户的角色信息。 
- getPassword: 获取密码。 
- getUserName: 获取用户名。 
- isAccountNonExpired: 账户是否过期。 
- isAccountNonLocked: 账户是否被锁定。 
- isCredentialsNonExpired: 密码是否过期。 
- isEnabled: 账户是否可用。 
UserDetailsService
提到了UserDetails就必须得提到UserDetailsService, UserDetailsService也是一个接口,且只有一个方法loadUserByUsername,他可以用来获取UserDetails。
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
通常在spring security应用中,我们会自定义一个CustomUserDetailsService来实现UserDetailsService接口,并实现其public UserDetails loadUserByUsername(final String login);方法。我们在实现loadUserByUsername方法的时候,就可以通过查询数据库(或者是缓存、或者是其他的存储形式)来获取用户信息,然后组装成一个UserDetails,(通常是一个org.springframework.security.core.userdetails.User,它继承自UserDetails) 并返回。
在实现loadUserByUsername方法的时候,如果我们通过查库没有查到相关记录,需要抛出一个异常来告诉spring security来“善后”。这个异常是org.springframework.security.core.userdetails.UsernameNotFoundException。
AuthenticationProvider
负责真正的验证。
当我们使用 authentication-provider 元素来定义一个 AuthenticationProvider 时,如果没有指定对应关联的 AuthenticationProvider 对象,Spring Security 默认会使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails,其中包括用户名、密码和所拥有的权限等。所以如果我们需要改变认证的方式,我们可以实现自己的 AuthenticationProvider;如果需要改变认证的用户信息来源,我们可以实现 UserDetailsService。
实现了自己的 AuthenticationProvider 之后,我们可以在配置文件中这样配置来使用我们自己的 AuthenticationProvider。其中 myAuthenticationProvider 就是我们自己的 AuthenticationProvider 实现类对应的 bean。
AuthenticationProvider 接口如下:
package org.springframework.security.authentication;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
public interface AuthenticationProvider {
    Authentication authenticate(Authentication var1) throws AuthenticationException;
    boolean supports(Class<?> var1);
}
- authenticate 表示认证的动作。 
- supports 表示所支持的 Authentication类型。Authentication 包含很多子类,如果 AbstractAuthenticationToken 。 
AbstractAuthenticationToken implements Authentication
还有,可以自定义 Authentication ,比如 本实例所使用的: JwtAuthenticationToken。
AuthenticationManager
认证是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中可以定义有多个 AuthenticationProvider。
AuthenticationManager 是一个接口,它只有一个方法,接收参数为Authentication,其定义如下:
public interface AuthenticationManager {
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
}
AuthenticationManager 的作用就是校验Authentication,如果验证失败会抛出AuthenticationException异常。AuthenticationException是一个抽象类,因此代码逻辑并不能实例化一个AuthenticationException异常并抛出,实际上抛出的异常通常是其实现类,如DisabledException,LockedException,BadCredentialsException等。BadCredentialsException可能会比较常见,即密码错误的时候。
组件比较多,但是如果主要流程理顺了,也比较简单。
Spring Security 实战
搞定两个 AuthenticationProvider:
(1) 从数据库获取用户
首先通过 UserDetailsService 获取 UserDetails,然后 通过 UserDetailsService 装配 DaoAuthenticationProvider
(2) 完成用户的认证
实现一个自己的 JwtAuthenticationProvider,完成用户的认证
(3)定制一个过滤器
(4)完成所有组件的装配
实战1 : UserDetailsService 获取 UserDetails
首先通过 UserDetailsService 获取 UserDetails,然后 通过 UserDetailsService 装配 DaoAuthenticationProvider。
package com.crazymaker.springcloud.user.info.service.impl;
@Slf4j
@Service
public class UserAuthService implements UserDetailsService {
    private PasswordEncoder passwordEncoder;
    public UserAuthService() {
        //默认使用 bcrypt, strength=10
        this.passwordEncoder =
                PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
    private UserPO loadFromDB(String username) {
        if (null == userDao)
        {
            userDao = CustomAppContext.getBean(UserDao.class);
        }
        List<UserPO> list = userDao.findAllByLoginName(username);
        if (null == list || list.size() <= 0) {
            return null;
        }
        UserPO userPO = list.get(0);
        return userPO;
    }
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        UserPO userPO = loadFromDB(username);
        //将salt放到password字段返回
        return User.builder()
                .username(userPO.getLoginName())
                .password(userPO.getPassword())
//                .password(SessionConstants.SALT)
                //BCrypt.gensalt();  正式开发时可以调用该方法实时生成加密的salt
//                .password(SessionConstants.SALT)
                .authorities(SessionConstants.USER_INFO)
                .roles("USER")
                .build();
    }
}
实战2: 装配 DaoAuthenticationProvider
在 SecurityConfiguration 配置类中加入如下内容:
    @Bean("daoAuthenticationProvider")
    protected AuthenticationProvider daoAuthenticationProvider() throws Exception {
        //这里会默认使用BCryptPasswordEncoder比对加密后的密码,注意要跟createUser时保持一致
        DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
        daoProvider.setUserDetailsService(userDetailsService());
        return daoProvider;
    }
    @Override
    protected UserDetailsService userDetailsService() {
        return new UserAuthService();
    }
实战3: 实现一个自己的 JwtAuthenticationProvider
继承于 AuthenticationProvider,实现一个自己的 JwtAuthenticationProvider,完成用户的认证
package com.crazymaker.springcloud.standard.security.provider;
//...
public class JwtAuthenticationProvider implements AuthenticationProvider {
    private RedisOperationsSessionRepository sessionRepository;
    private CustomedSessionIdResolver httpSessionIdResolver;
    public JwtAuthenticationProvider(RedisOperationsSessionRepository sessionRepository,
                                     CustomedSessionIdResolver httpSessionIdResolver) {
        this.sessionRepository = sessionRepository;
        this.httpSessionIdResolver = httpSessionIdResolver;
    }
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        DecodedJWT jwt = ((JwtAuthenticationToken) authentication).getToken();
        if (jwt.getExpiresAt().before(Calendar.getInstance().getTime())) {
            throw new NonceExpiredException("认证过期");
        }
        String sid = jwt.getSubject();
        String otoken = jwt.getToken();
        Session session = null;
        try {
            session = sessionRepository.findById(sid);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (null == session) {
            throw new NonceExpiredException("认证有误,请重新登录");
        }
        String json = session.getAttribute(G_USER);
        if (StringUtils.isBlank(json)) {
            throw new NonceExpiredException("认证有误,请重新登录");
        }
        UserDTO userDTO = JsonUtil.jsonToPojo(json, UserDTO.class);
        if (null == userDTO) {
            throw new NonceExpiredException("认证有误");
        }
        String password = userDTO.getPassword();
        String username = userDTO.getLoginName();
        UserDetails user = User.builder()
                .username(username)
                .password(password)
                .authorities(SessionConstants.USER_INFO)
                .build();
        String encryptSalt = password;
        try {
            Algorithm algorithm = Algorithm.HMAC256(encryptSalt);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withSubject(sid).build();
            verifier.verify(jwt.getToken());
        } catch (Exception e) {
            throw new BadCredentialsException("JWT token verify fail", e);
        }
        JwtAuthenticationToken token =
                new JwtAuthenticationToken(user, jwt, user.getAuthorities());
        return token;
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.isAssignableFrom(JwtAuthenticationToken.class);
    }
}
实战4: 装配 AuthenticationManager
认证是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中可以定义有多个 AuthenticationProvider。
  @EnableWebSecurity()
public class UserWebSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider())
                .authenticationProvider(jwtAuthenticationProvider());
    }
    //....
}
实战5: 定制过滤器,将 AuthenticationManager 用起来
搞得再多,如果不通过过滤器,将 AuthenticationManager 用起来,也是没有用的。
package com.crazymaker.springcloud.standard.security.filter;
//.....
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private RequestMatcher requiresAuthenticationRequestMatcher;
    private List<RequestMatcher> permissiveRequestMatchers;
    private AuthenticationManager authenticationManager;
    private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
     //.....
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        Authentication authResult = null;
        /**
         * 场景: 从 zuul 过来,直接带上session 头
         */
        if (StringUtils.isNotEmpty(request.getHeader(SessionConstants.SESSION_SEED))) {
            request.setAttribute(SessionConstants.SESSION_SEED,
                    request.getHeader(SessionConstants.SESSION_SEED));
            UserDetails userDetails = User.builder()
                    .username(request.getHeader(SessionConstants.SESSION_SEED))
                    .password(request.getHeader(SessionConstants.SESSION_SEED))
                    .authorities(SessionConstants.USER_INFO)
                    .build();
            authResult = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
            successfulAuthentication(request, response, filterChain, authResult);
            filterChain.doFilter(request, response);
            return;
        }
        /**
         * 正常场景: 单体微服务访问,或者从Zuul过来,没有带 session head
         */
        if (!requiresAuthentication(request, response)) {
            filterChain.doFilter(request, response);
            return;
        }
        AuthenticationException failed = null;
        try {
            String token = getJwtToken(request);
            if (StringUtils.isNotBlank(token)) {
                JwtAuthenticationToken authToken = new JwtAuthenticationToken(JWT.decode(token));
                DecodedJWT jwt = authToken.getToken();
                //将  AuthenticationManager 用起来
                authResult = this.getAuthenticationManager().authenticate(authToken);
                UserDetails user = (UserDetails) authResult.getPrincipal();
                request.setAttribute(SessionConstants.SESSION_SEED, jwt.getSubject());
            } else {
                failed = new InsufficientAuthenticationException("请求头认证消息为空");
            }
        } catch (JWTDecodeException e) {
            logger.error("JWT format error", e);
            failed = new InsufficientAuthenticationException("请求头认证消息格式错误", failed);
        } catch (InternalAuthenticationServiceException e) {
            logger.error(
                    "An internal error occurred while trying to authenticate the user.",
                    failed);
            failed = e;
        } catch (AuthenticationException e) {
            // Authentication failed
            failed = e;
        }
        if (authResult != null) {
            successfulAuthentication(request, response, filterChain, authResult);
        } else if (!permissiveRequest(request)) {
            unsuccessfulAuthentication(request, response, failed);
            return;
        }
        filterChain.doFilter(request, response);
    }
    protected void unsuccessfulAuthentication(HttpServletRequest request,
                                              HttpServletResponse response, AuthenticationException failed)
            throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        failureHandler.onAuthenticationFailure(request, response, failed);
    }
 //....
}
实战6: 配置 HttpSecurity 的过滤机制
还是在 UserWebSecurityConfig 配置文件,将 HttpSecurity 的过滤机制配置起来,完成所有组件的装配。
代码如下:
package com.crazymaker.springcloud.user.info.config;
//...
@EnableWebSecurity()
public class UserWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private UserAuthService userAuthService;
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers(
                        "/v2/api-docs",
                        "/swagger-resources/configuration/ui",
                        "/swagger-resources",
                        "/swagger-resources/configuration/security",
                        "/swagger-ui.html",
                        "/api/user/login/v1",
//                        "/api/user/add/v1",
//                        "/api/user/speed/test/v1",
//                        "/api/user/say/hello/v1",
//                        "/api/user/*/detail/v1",
                        "/api/crazymaker/duty/info/user/login")
                .permitAll()
                .anyRequest().authenticated()
//                .antMatchers("/image/**").permitAll()
//                .antMatchers("/admin/**").hasAnyRole("ADMIN")
                .and()
                .formLogin().disable()
                .sessionManagement().disable()
                .cors()
                .and()
                .addFilterAfter(new OptionsRequestFilter(), CorsFilter.class)
                .apply(new JsonLoginConfigurer<>()).loginSuccessHandler(jsonLoginSuccessHandler())
                .and()
                .apply(new JwtAuthConfigurer<>()).tokenValidSuccessHandler(jwtRefreshSuccessHandler()).permissiveRequestUrls("/logout")
                .and()
                .logout()
//		        .logoutUrl("/logout")   //默认就是"/logout"
                .addLogoutHandler(tokenClearLogoutHandler())
                .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
                .and()
                .addFilterBefore(springSessionRepositoryFilter(), SessionManagementFilter.class)
                .sessionManagement().disable()
        ;
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                "/api/user/login/v1",
                "/v2/api-docs",
                "/swagger-resources/configuration/ui",
                "/swagger-resources",
                "/swagger-resources/configuration/security",
//                "/api/user/say/hello/v1",
//                "/api/user/add/v1",
//                "/api/user/speed/test/v1",
//                "/api/user/*/detail/v1",
                "/images/**",
                "/swagger-ui.html",
                "/webjars/**",
                "**/favicon.ico",
                "/css/**",
                "/js/**",
                "/api/crazymaker/info/user/login"
        );
    }
    @Resource
    RedisOperationsSessionRepository sessionRepository;
    @Resource
    public CustomedSessionIdResolver httpSessionIdResolver;
    @DependsOn({"sessionRepository", "httpSessionIdResolver"})
    @Bean("jwtAuthenticationProvider")
    protected AuthenticationProvider jwtAuthenticationProvider() {
        return new JwtAuthenticationProvider(sessionRepository, httpSessionIdResolver);
    }
    public <S extends Session> OncePerRequestFilter springSessionRepositoryFilter() {
        CustomedSessionRepositoryFilter<? extends Session> sessionRepositoryFilter = new CustomedSessionRepositoryFilter<>(
                sessionRepository);
//        sessionRepositoryFilter.setServletContext(this.servletContext);
        sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
        return sessionRepositoryFilter;
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider())
                .authenticationProvider(jwtAuthenticationProvider());
    }
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Bean("daoAuthenticationProvider")
    protected AuthenticationProvider daoAuthenticationProvider() throws Exception {
        //这里会默认使用BCryptPasswordEncoder比对加密后的密码,注意要跟createUser时保持一致
        DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
        daoProvider.setUserDetailsService(userDetailsService());
        return daoProvider;
    }
    @Bean
    protected JwtRefreshSuccessHandler jwtRefreshSuccessHandler() {
        return new JwtRefreshSuccessHandler();
    }
    @Override
    protected UserDetailsService userDetailsService() {
        return new UserAuthService();
    }
    @Bean
    protected JsonLoginSuccessHandler jsonLoginSuccessHandler() {
        return new JsonLoginSuccessHandler(userAuthService);
    }
    @Bean
    protected TokenClearLogoutHandler tokenClearLogoutHandler() {
        return new TokenClearLogoutHandler(userAuthService);
    }
    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "HEAD", "OPTION"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.addExposedHeader(SessionConstants.AUTHORIZATION);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
实战小结
大概通过以上6步,一个集成jwt的springsecurity机制,完整的配置起来了。
具体,请关注 Java 高并发研习社群 【博客园 总入口 】
最后,介绍一下疯狂创客圈:疯狂创客圈,一个Java 高并发研习社群 【博客园 总入口 】
疯狂创客圈,倾力推出:面试必备 + 面试必备 + 面试必备 的基础原理+实战 书籍 《Netty Zookeeper Redis 高并发实战》
疯狂创客圈 Java 死磕系列
- Java (Netty) 聊天程序【 亿级流量】实战 开源项目实战
- Netty 源码、原理、JAVA NIO 原理
- Java 面试题 一网打尽
- 疯狂创客圈 【 博客园 总入口 】
Java 面试题 一网打尽**
- 疯狂创客圈 【 博客园 总入口 】
spring security 原理+实战的更多相关文章
- Spring Security原理分析:系列集合
		Spring Security 工作原理概览:https://blog.csdn.net/u012702547/article/details/89629415 spring security执行原理 ... 
- Spring Security原理与应用
		Spring Security是什么 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置 ... 
- spring security原理
		spring security通过一系列过滤器实现其功能,入口过滤器如下(web.xml): <filter> <filter-name>springSecurityFilte ... 
- spring security原理-学习笔记1-整体概览
		整体概述 运行时环境 Spring Security 3.0需要Java 5.0 Runtime Environment或更高版本. 核心组件 SecurityContextHolder,Securi ... 
- spring security原理-学习笔记2-核心组件
		核心组件 AuthenticationManager,ProviderManager和AuthenticationProvider AuthenticationManager只是一个接口,实际中是如何 ... 
- Spring Security原理篇(一) 启动原理
		1.概述 spring security有参考的中文翻译文档https://springcloud.cc/spring-security-zhcn.html 在学习spring security的时候 ... 
- spring security 简介+实战
		过滤器链: 依赖: security 功能列表: 一.登录验证.权限验证 1.1 httpbasic验证 1.2form验证 建立数据需要遵循RBAC模型 用户表要参考UserDetail创建 实例类 ... 
- 214. Spring Security:概述
		前言 在之前介绍过了Shiro之后,有好多粉丝问SpringSecurity在Spring Boot中怎么集成.这个系列我们就和大家分享下有关这方面的知识. 本节大纲 一.什么是SpringSecur ... 
- Spring Security编程模型
		1.采用spring进行权限控制 url权限控制 method权限控制 实现:aop或者拦截器(本质就是之前之后进行控制)--------------------proxy就是 2.权限模型: 本质理 ... 
随机推荐
- C语言I作业09
			问题 回答 这个作业属于哪个课程 C语言程序设计II 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/SE2019-2/homework/10032 我在 ... 
- Spring Cloud第三篇 | 搭建高可用Eureka注册中心
			 本文是Spring Cloud专栏的第三篇文章,了解前两篇文章内容有助于更好的理解后面文章: Spring Cloud第一篇 | Spring Cloud前言及其常用组件介绍概览 Spring ... 
- css重置样式
			<style> html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, ... 
- 转:java 解析excel,带合并单元的excel
			收集了一些对博主有帮助的博文,如下 >>>>>>>>>>>第一部分: 首先,mavn导入jar包 <!-- 解析excel需要导 ... 
- postman tests常用方法
			postman常用方法集合: 1.设置环境变量 postman.setEnvironmentVariable("key", "value"); pm.envir ... 
- iOS10 openURL方法跳转到设置界面
			问题 在iOS10之前,跳转到系统设置界面的某个指定界面的方式如下: //打开定位服务界面 NSURL*url=[NSURL URLWithString:@"prefs:root=Priva ... 
- go基础之基本数据结构(数组、slice、map)
			go基本的数据结构有数组.slice.map,高级数据结构为结构体为用户自定义类型.本片文章主要讲解三大基本数据结构. 数组 slice Map 数组 数组是包含单个类型的元素序列,但是长度固定的数据 ... 
- LightOJ1355 Game Of CS(green 博弈)
			Jolly and Emily are two bees studying in Computer Science. Unlike other bees they are fond of playin ... 
- 搭建本地YUM仓库
			YUM介绍 yum(yellow dog updater modified)为多个Linux发行版的软件包管理工具,Redhat RHEL CentOS Fedora YUM主要用于自动安装,升级rp ... 
- art-template模板判断
			1.添加模板 <script id="userinfo" type="text/template"> {{ if id == n ... 
