SpringSecurity 基本使用方法(JWT以及基本表单验证)

SpringSecurity 使用方法

一、本地视图方式校验(Thymeleaf)

1、基本用户名校验

  1. 配置Pom依赖

    //父工程
     	<parent>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-parent</artifactId>
        	<version>2.6.4</version>
    	</parent>
    //依赖
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
        </dependencies>
    
  2. 配置application.yaml

    spring:
      application:
        name: springboot-security-test
    server:
      port: 8899
    
  3. 配置SpringBootApplication主启动

    @SpringBootApplication
    public class SpingBootApplicationMain {
        public static void main(String[] args) {
            SpringApplication.run(SpingBootApplicationMain.class, args);
        }
    }
    
  4. 编写Controller

    @RestController
    @RequestMapping("/sec")
    public class TestController {
        @GetMapping("hello")
        public String hello(){
            return "认证成功";
        }
    }
    

1.1、从配置文件中进行校验

修改配置文件中的application.yaml添加Security配置

security:
 user:
   name: byz
   password: 123

启动工程进行验证,访问配置的映射器路径:

image-20220414095444309

进入登陆页面,输入在配置文件中的用户名密码进入

image-20220414095725437

1.2、从配置类中进行校验

1、修改配置文件,注释掉:

#  security:
#    user:
#      name: byz
#      password: 123

2、新建一个配置类

/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description TODO
 * @Date 2022-04-14 09:43:31
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //注入密码加密编码器
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
	
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder passwordEncoder = (BCryptPasswordEncoder) passwordEncoder();
        String password = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("byz").password(password).roles("admin");
    }
}

1.3、从数据库或者其他方式自定义校验UserDetailService

  1. 自定义USerDeatilService实现UserdetailService接口

    @Service("userDetailsService") //名称必须叫这个
    public class MyUserDetailsServiceImpl implements UserDetailsService {
        //这里注入数据库查询逻辑,实际校验用户是否存在
        //注入mapper进行校验
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            /*
             * 1、使用Mapper查询数据库
             * 2、判断是否查询到用户
             * (这里假定查询结果)
             * */
            MyUser user = new MyUser();
            List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user,ROLE_admin");
            if (user != null) {
                /**
                 * 从数据库查询到的用户
                 * 实质上需要返回一个数据库查询到的用户
                 * 并将其封装成为一个UserDetail对象进行返回,系统会自动校验其中的密码以及用户名信息
                 */
                return new User("byz", new BCryptPasswordEncoder().encode("123"), grantedAuthorities); //这里USer为UserDetail的子类
            } else {
                //查询不到就报错
                throw new UsernameNotFoundException("用户名不存在!");
            }
        }
    }
    
  2. 设置配置类并且重写配置权限方法configure(AuthenticationManagerBuilder auth)

    /**
     * @Author BaiYZ
     * @Program SpringSecurityDemo
     * @Description TODO
     * @Date 2022-04-14 09:43:31
     */
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        //注入密码编码器
        @Bean
        PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
        
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //设置自定义的USerDetailService逻辑,以及配置密码编码器
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
    }
    
  3. 配置类中配置Httpl拦截方法

    /**
         * 重写http加密校验规则
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    //配置登陆也米娜
                    .formLogin() //自定义登陆页面 ,这里会调用UserNamePasswordFilter
                    .loginPage("/user/login")
                    //登陆访问url地址  由控制器处理
                    .loginProcessingUrl("/login")
                    //登陆成功url地址 由控制器处理
                    .defaultSuccessUrl("/user/success")
    
                    //配置路径权限
                    .and()
                    .authorizeRequests() //表示配置地址权限访问
                    //配置以路径匹配来怕配置权限
                    .antMatchers("/", "/index", "/user/login").permitAll()
    
                    //配置指定页面使用角色管理
                    .antMatchers("/role/admin").hasRole("admin")
                    .antMatchers("/role/service").hasAnyRole("user,test")
                    //指定权限管理
                    .antMatchers("/role/auth").hasAuthority("admin")
                    .antMatchers("/role/test").hasAnyAuthority("test")
    
                    //对请求进行配置 POST Get ....
                    .anyRequest().authenticated() //所有请求都需要授权
    
                    //csrf跨站请求配置
                    .and()
                    .csrf().disable()
    
                    //配置403 自定义逻辑
                    //403自定义错误url
                    .exceptionHandling().accessDeniedPage("/unauth");
    
        }
    

2、记住我rememberMe

2.1、记住我(基于数据库)

  1. 注入数据源

  2. 安全配置类中配置(注入)PersistentTokenRepository

     	@Bean
        public PersistentTokenRepository persistentTokenRepository(){
            JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
            //启动时创建表,创建一次,也可以手动创建(进入这个PersistentTokenRepository获取建表的sql)
            jdbcTokenRepository.setCreateTableOnStartup(true);
            return jdbcTokenRepository;
        }
    
  3. 配置类中

     protected void configure(HttpSecurity http) throws Exception {
         http
             .and()
             .rememberMe().tokenRepository(persistentTokenRepository()) //开启RememberMe设置操作数据库对象
             .tokenValiditySeconds(60) //设置过期时间 
             .userDetailsService(userDetailsService); //设置查询数据库的逻辑板 返回UserDetails
             //其他配置....
     }
    
  4. 页面配置复选框

    名称必须叫做remember-me 框架底层设置

    <input type="checkbox" name="remember-me"/>
    

3、退出登陆(默认行为)

默认的退出登录URL为/logout

需要自定义就实现LogoutHandler接口

二、分布式Token校验

2.1、编写UserDetail对象(UserDetailService查询会使用到)

  1. 编写基础用户对象(存储数据库中)

    /**
     * @Author BaiYZ
     * @Program SpringSecurityDemo
     * @Description 实际的实体类
     * @Date 2022-04-15 17:01:52
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User implements Serializable {
        @Serial
        private static final long serialVersionUID = 2211L;
        private String userName;
        private String password;
        private String age;
        private String birthday;
    }
    
  2. 编写SecurityUser用于SpringSecurity中权限用户封装

    /**
     * @Author BaiYZ
     * @Program SpringSecurityDemo
     * @Description 用于鉴权的实体类
     * @Date 2022-04-15 17:02:31
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class SecurityUser implements UserDetails {
        /**
         * 当前用户
         */
        private transient User currentUserInfo;
        /**
         * 权限列表
         */
        private List<String> permissionValueList;
    
        public SecurityUser(User currentUserInfo) {
            if(currentUserInfo != null){
                this.currentUserInfo = currentUserInfo;
            }
        }
    
        /**
         * 获取授权列表 GrantedAuthority
         * @return  Collection<? extends GrantedAuthority>
         */
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            Assert.notNull(this.permissionValueList, "Cannot pass a null GrantedAuthority collection");
            //遍历设置权限
            Set<GrantedAuthority> setAuthorities = new HashSet<>();
            for (String grantedAuthority : this.permissionValueList) {
                //权限不为空
                if(StringUtils.hasText(grantedAuthority)){
                    SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(grantedAuthority);
                    setAuthorities.add(simpleGrantedAuthority);
                }
            }
            return setAuthorities;
        }
    
        @Override
        public String getPassword() {
            return null;
        }
    
        @Override
        public String getUsername() {
            return null;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return false;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return false;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return false;
        }
    
        @Override
        public boolean isEnabled() {
            return false;
        }
    }
    

2.2、编写Security

image-20220415163947241

2.1.1、编写自定义密码加密规则PassWordEncoder

/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description TODO
 * @Date 2022-04-15 14:56:21
 */
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;

    public DefaultPasswordEncoder() {
    }

    public DefaultPasswordEncoder(int strength) {

    }

    /**
     * 自定义加密方式  --->> 也可以使用md5加密,返回加密字符串即可
     * @param rawPassword 原始密码由Securuty框架传入
     * @return
     */
    @Override
    public String encode(CharSequence rawPassword) {
        return bCryptPasswordEncoder.encode(rawPassword);
    }

    /**
     * 比对密码比较的是输入后加密与存储的加密密码比对
     * @param rawPassword 框架获取的用户提交的密码
     * @param encodedPassword 存储的加密后的密码
     * @return bool 真为通过
     */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        //输入密码加密
        String encodeInput = bCryptPasswordEncoder.encode(rawPassword);
        return encodedPassword.equals(encodeInput);
    }
}

2.1.2 Token工具类

  • 生成Token(jwt生成—JsonWebToken)
  • 根据用户名获取Token
  • Token中获取用户名
  • 删除Token

Jwt介绍:

  1. JWT头

    1. alg:表示签名算法
    2. typ:令牌类型
    {
        "alg":"HS256",
        "typ":"JWT"
    }
    
  2. 有效载荷 PayLoad(Claims)

    1. iss:发行人
    2. exp:到期时间
    3. sub:主题
    4. aud:用户
    5. nbf:在此之前不可用
    6. iat:发布时间
    7. jti:jwt id 标识一个JWt
    8. 私有字段
    {
        "iss":"xxx",
        "exp":"222",
        "sub":"loginedUSer",
        "nbf":"xxx",
        "aud":"user",
        "iat":"xxx",
        "jti":"xxx",
        "自定义字段":"value"
    }
    
  3. 签名哈希

    对上面两部分进行签名,使用指定算法生成前面哈希

    例子:

    HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
    

完整JWt字符串:

image-20220415151923617

拼装结构:

JWTString=Base64(Header).Base64(Payload).签名哈希
2.1.2.1、导入JWt依赖
<!--父工程-->
<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.6.4</version>
</parent>       

<!--JWT依赖-->
<dependency>
   <groupId>io.jsonwebtoken</groupId>
   <artifactId>jjwt</artifactId>
   <version>0.9.1</version>
</dependency>
2.1.2.2、编写TokenManager工具类
/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description token 管理工具类
 * @Date 2022-04-15 15:25:04
 */
@Component("tokenManager")
public class TokenManager {
    /**
     * token有效期 这里设置60min
     */
    private long tokenExpiredTime = 60 * 60 * 1000;

    /**
     * token密钥
     * <b>一般实际项目中需要实际生成</b>
     */
    private String tokenSecrect = "111111";

    /**
     * 根据Token获取用户名信息
     * setSubject(String sub); 设置jwt加密对象
     * setExpiration(Date exp); 设置过期时间
     * signWith(SignatureAlgorithm alg, String base64EncodedSecretKey); 设置签名算法,签名密钥
     * CompressionCodecs.GZIP 进行压缩
     */
    public String createToken(String userName){
        String token = Jwts.builder()
                .setSubject(userName)
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiredTime))
                .signWith(SignatureAlgorithm.ES512,tokenSecrect)
                .compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }
    //根据用户名获取Token

    /**
     * Jwts.parser():获取jwt转化器
     * setSigningKey(String key):设置签名密钥
     * parseClaimsJws(String claimsJws):转化jwt
     * getBody():获取body
     * @param token 传入token串
     * @return 返回信息
     */
    public String getUserInfoFromToken(String token){
        String userInfo = Jwts.parser()
                .setSigningKey(tokenSecrect)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
        return userInfo;
    }
    public void removeToken(String token){

    }
}

2.1.3 退出登陆处理器(Token)

2.1.3.1、使用Redis存储Token
  1. 引入Pom依赖
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. application.yaml中配置Redis
spring:
  redis:
    port: 6379
    host: localhost
2.1.3.2、编写退出处理器
/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description Token退出登陆逻辑
 * @Date 2022-04-15 16:11:20
 */
@Component("tokenLogOutHandler")
public class TokenLogOutHandler implements LogoutHandler {

    /**
     * 注入Token工具类
     */
    private TokenManager tokenManager;
    /**
     * redis模板类
     */
    private RedisTemplate redisTemplate;

    public TokenLogOutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 退出登陆逻辑
     * @param request 请求
     * @param response 响应
     * @param authentication 授权信息封装
     */
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        //1、从header拿到Token
        String token = request.getHeader("token");
        //2、存在Token
        //2.1、移出Token(从Redis删除)
        if(token!=null){
            tokenManager.removeToken(token);
            String userInfoFromToken = tokenManager.getUserInfoFromToken(token);
            redisTemplate.delete(userInfoFromToken);
        }
        //写响应相关操作,返回一个状态xxx
    }
}

退出逻辑配置

@Override 
protected void configure(HttpSecurity http) throws Exception {
     http
         .and()
         .logout()
         .logoutUrl("/logout") //默认为logout
         .logoutSuccessHandler(tokenLogOutHandler) //自定义退出成功处理器
         .deleteCookies("JSESSIONID") //要删除的Cookies
}  

2.1.4 未授权统一处理AuthenticationEntryPoint

/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description 未授权统一处理
 * @Date 2022-04-15 16:32:12
 */
@Component("unAuthenticationEntryPoint")
public class UnAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        //处理未授权逻辑
    }
}

未授权统一权限配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
  http
  //没有权限处理器
  .exceptionHandling().authenticationEntryPoint(unAuthenticationEntryPoint)
}

2.3、编写过滤器

image-20220415191708836

2.3.1、认证过滤器

/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description 认证过滤器
 * @Date 2022-04-15 16:47:42
 */
@Component("tokenLoginFilter")
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    /**
     * redis模板类
     */
    private RedisTemplate redisTemplate;
    /**
     * token工具类
     */
    private TokenManager tokenManager;
    /**
     * 权限管理工具类
     */
    private AuthenticationManager authenticationManager;

    public TokenLoginFilter(RedisTemplate redisTemplate, TokenManager tokenManager, AuthenticationManager authenticationManager) {
        this.redisTemplate = redisTemplate;
        this.tokenManager = tokenManager;
        this.authenticationManager = authenticationManager;
        //设置不止用post提交
        this.setPostOnly(false);
    }

    /**
     * 重写尝试认证的方法
     * 父类会进行调用
     *
     * @param request  请求
     * @param response 响应
     * @return Authentication 返回认证封装
     * @throws AuthenticationException 认证异常
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //获取表单提交数据
        try {
            //从请求中获取用户信息
            User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
            //交给SpringSecurity管理
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword(), new ArrayList<>()));
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    /**
     * AbstractAuthenticationProcessingFilter调用子类attemptAuthentication认证之后
     * <b>成功调用</b>
     *
     * @param request    请求
     * @param response   响应
     * @param chain      过滤器链
     * @param authResult 认证结果Authentication
     * @throws IOException      id异常
     * @throws ServletException servlet异常
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        //获取认证之后用户信息UserDetail
        SecurityUser securityUser = (SecurityUser) authResult.getPrincipal();
        //封装token(这里使用用户名生成)
        String token = tokenManager.createToken(securityUser.getCurrentUserInfo().getUserName());
        //将用户名以及权限列表放入Redis
        redisTemplate.opsForValue().set(token, securityUser.getPermissionValueList());
		
        //将Token 返回前端!!!!前端会进行封装放到请求头中
        
        
        //返回相关页面信息

    }

    /**
     * AbstractAuthenticationProcessingFilter调用子类attemptAuthentication认证之后
     * <b>成功调用</b>
     *
     * @param request  请求
     * @param response 响应
     * @param failed   失败的认证异常
     * @throws IOException      id异常
     * @throws ServletException servlet异常
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        //认证失败的逻辑 比如返回页面error json
    }
}

配置认证过滤器:

@Override
protected void configure(HttpSecurity http) throws Exception {
  //增加自定义认证过滤器
  http.addFilter(tokenLoginFilter)
}

2.3.2、编写授权过滤器

/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description 授权过滤器
 * @Date 2022-04-15 16:47:15
 */
@Component("tokenAuthenticationFilter")
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //获取当前认证成功用户权限信息
        UsernamePasswordAuthenticationToken authResult = getAuthentication(request);
        //判断存在权限信息,放到权限上下文
        if (authResult != null) {
            SecurityContextHolder.getContext().setAuthentication(authResult);
        }
        //放行过滤器
        chain.doFilter(request, response);
    }

    /**
     * 从请求中获取解析Token生成UsernamePasswordAuthenticationToken
     *
     * @param request 请求
     * @return UsernamePasswordAuthenticationToken
     */
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        //拿到token 
        String token = request.getHeader("token");
        if (token != null) {
            //从token 解析用户名
            String username = tokenManager.getUserInfoFromToken(token);
            //从redis中获取权限列表
            List<String> permissionList = (List<String>) redisTemplate.opsForValue().get("username");
            List<GrantedAuthority> collect = null;
            if (permissionList != null) {
                collect = permissionList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
            }
            return new UsernamePasswordAuthenticationToken(username, token, collect);
        }
        return null;
    }

}

配置授权过滤器:

@Override
protected void configure(HttpSecurity http) throws Exception {
  //增加自定义授权过滤器
  http.addFilter(tokenAuthenticationFilter)
}

2.4、编写UserDetailService

/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description 实际查询数据库获取UserDetail
 * @Date 2022-04-15 18:54:03
 */
@Service("userDetailService") //名称必须叫这个,否则无法注入
public class UserDetailServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询数据库,获取Security需要的对象

        //返回UserDetail对象
        return new SecurityUser();
    }
}

配置自定义UserDetailService:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  //增加自定义授权过滤器
  auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

2.5、编写核心配置类

/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description token 核心配置类
 * @Date 2022-04-15 18:22:00
 */
@Configuration
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 授权失败(为授权)统一处理器
     */
    private final UnAuthenticationEntryPoint unAuthenticationEntryPoint;
    /**
     * 退出处理器
     */
    private final TokenLogOutHandler tokenLogOutHandler;
    /**
     * 认证过滤器
     */
    private final TokenLoginFilter tokenLoginFilter;
    /**
     * 授权过滤器
     */
    private final TokenAuthenticationFilter tokenAuthenticationFilter;
  	/**
     * 实际查询数据库获取UserDetail的逻辑
     */
    private final UserDetailsService userDetailsService;

    @Autowired
    public TokenWebSecurityConfig(UnAuthenticationEntryPoint unAuthenticationEntryPoint, TokenLogOutHandler tokenLogOutHandler, TokenLoginFilter tokenLoginFilter,
                                  TokenAuthenticationFilter tokenAuthenticationFilter,UserDetailsService userDetailsService) {
        this.unAuthenticationEntryPoint = unAuthenticationEntryPoint;
        this.tokenLogOutHandler = tokenLogOutHandler;
        this.tokenLoginFilter = tokenLoginFilter;
        this.tokenAuthenticationFilter = tokenAuthenticationFilter;
        this.userDetailsService = userDetailsService;
    }

    /**
     * 整体配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //没有权限处理器
                .exceptionHandling().authenticationEntryPoint(unAuthenticationEntryPoint)

                //自定义退出成功处理器
                .and()
                .logout()
                .logoutUrl("/logout") //默认为logout
                .addLogoutHandler(tokenLogOutHandler)
                .deleteCookies("JSESSIONID") //要删除的Cookies


                //配置所有的需要授
                .and()
                .authorizeRequests()
                //这里增加需要hasAuth权限或者需要角色的地址认证
                //配置以路径匹配来怕配置权限
//                .antMatchers("/", "/index", "/user/login").permitAll()
//
//                //配置指定页面使用角色管理
//                .antMatchers("/role/admin").hasRole("admin")
//                .antMatchers("/role/service").hasAnyRole("user,test")

                .anyRequest().authenticated()

                //关闭csrf跨域攻击
                .and().csrf().disable()

                //增加自定义认证过滤器
                .addFilter(tokenLoginFilter)

                //增加权限过滤器
                .addFilter(tokenAuthenticationFilter);
    }
    /**
     * 配置权限验证相关
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
    }

    /**
     * 设置访问路径相关的
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        //设置不进行权限校验的路径
        web.ignoring().antMatchers("/");
    }
    
}

三、Handler配置

3.1、授权成功处理器

1、编写处理器

/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description 登陆成功处理逻辑
 * @Date 2022-04-15 15:52:27
 */
@Component("authenticationSuccessHandlerImpl")
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //登陆成功的逻辑
    }
}

2、配置到Security配置文件中

@Override 
protected void configure(HttpSecurity http) throws Exception {
        http.successHandler(authenticationSuccessHandlerImpl);
 }

3.2、授权失败处理器

1、编写处理器

/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description 授权失败处理器
 * @Date 2022-04-15 15:57:03
 */
@Component("authenticationFailureHandlerImpl")
public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        //授权失败的逻辑
    }
}

2、配置到Security配置文件中

@Override 
protected void configure(HttpSecurity http) throws Exception {
        http.failureHandler(authenticationFailureHandlerImpl);
 }

3.3、退出登陆处理器

1、编写处理器

/**
 * @Author BaiYZ
 * @Program SpringSecurityDemo
 * @Description 退出登陆处理器
 * @Date 2022-04-15 16:02:26
 */
@Component("logoutSuccessHandlerImpl")
public class LogoutSuccessHandlerImpl implements LogoutHandler {

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        //退出登陆的逻辑
    }
}

2、配置到Sevurity配置文件中

@Override 
protected void configure(HttpSecurity http) throws Exception {
        http
            .and()
            .logout()
            .logoutUrl("/logout") //默认为logout
            .logoutSuccessHandler(logoutSuccessHandlerImpl) //自定义退出成功处理器
            .deleteCookies("JSESSIONID") //要删除的Cookies
} 

四、权限注解

4.1、开启权限注解

@Configuration
/**
 * 开启全局方法权限注解  
 * securedEnabled:开启安全注解
 * prePostEnabled:前置后置过滤注解
 */
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

4.2、常用注解使用

4.2.1、@Secured

适用于方法上,标识当前这个方法是什么样的角色可以访问

    /**
     * securedEnable测试 开启角色权限
     *
     * @return
     */
    @ResponseBody
    @GetMapping("/securedEnabled")
    @Secured({"ROLE_test"}) //允许test角色访问
    public String securedEnabledTest() {
        return "允许访问,这里是 @EnableGlobalMethodSecurity(securedEnabled = true) 注解配置使用@Secure";
    }

4.2.2、@PreAuthorize

适用于方法上,可以用权限或者角色,先校验权限,后执行方法 不通过403

    /**
     * prePostEnabled测试 可以只用权限或者角色,先校验权限,后执行方法 不通过403
     *
     * @return
     */
    @ResponseBody
    @GetMapping("/prePostEnabled")
    @PreAuthorize("hasAnyAuthority('admin')")
    public String prePostEnabled() {
        return "允许访问,这里是 @EnableGlobalMethodSecurity(prePostEnabled = true) 注解配置使用 提前校验@PreAuthorize";
    }

4.2.3、@PostAuthorize

适用于方法上,有任何权限,先执行方法 后校验权限,不通过403

   /**
     * prePostEnabled测试 有任何权限,先执行方法 后校验权限
     *
     * @return
     */
    @ResponseBody
    @GetMapping("/postEnabled")
    @PostAuthorize("hasAnyAuthority('test')")
    public String postAuthorize() {
        return "允许访问,这里是 @EnableGlobalMethodSecurity(prePostEnabled = true) 注解配置使用 后置校验@PostAuthorize";
    }

4.2.4、@PostFilter

适用于方法上,输出匹配的指定值(留下这个其他过滤)

    /**
     * 权限校验之后,输出匹配的指定值(留下这个其他过滤)
     *
     * @return
     */
    @ResponseBody
    @RequestMapping("/postFilter")
    @PostFilter("filterObject == 'admin'")
    public List<String> getAllListPostFilter() {
        ArrayList<String> list = new ArrayList<>();
        list.add("admin");
        list.add("test");
        return list;
    }

会输出 admin , test被过滤掉

4.2.4、@PreFilter

适用于方法上,权限校验之后,输出匹配的指定值

    /**
     * 权限校验之后,输出匹配的指定值
     *
     * @return
     */
    @ResponseBody
    @RequestMapping("/preFilter")
    @PreFilter("filterObject == 'test'")
    public List<String> getAllListPreFilter(@RequestBody List<String> input) {
        return input;
    }

输入:

[
	"test","admin","user","abcd"
]

输出:

[
    "test"
]

五、坑

  1. 角色配置中不需要增加“ROLE_”前缀,但是注解中需要增加,数据库中不用增加

    		public ExpressionInterceptUrlRegistry hasRole(String role) {
    			return access(ExpressionUrlAuthorizationConfigurer
                              //这里会传入前缀
    					.hasRole(ExpressionUrlAuthorizationConfigurer.this.rolePrefix, role));
    		}
    

    this.rolePrefix在构造方法中进行设置,如果没有覆盖默认的设置GrantedAuthorityDefaults.class会使用前缀

    image-20220415192937614

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不会拯救世界的SuperMan

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

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

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

打赏作者

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

抵扣说明:

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

余额充值