SpringSecurity框架-01

1.框架简介

2.SpringSecurity入门案例

3.SpringSecurity权限方案

4.SpringSecurity微服务权限方案

5.SpringSecurity原理总结

 

1.1概要

SpringSecurity提供了一套Web应用安全性的完整解决方案

(1)认证 (2)授权

 

和Shiro的比较 Shiro功能不如SpringSecurity强大 但是通用性更强 配置简单

一般搭配是SSM+Shiro

SpringBoot+SpringSecurity

 

1.2入门案例

 

在Security中默认用户名是user 默认密码是enerated security password

 

1.3基本原理
SpringSecurity本质上是一个过滤器链

 

①FilterSecurityInterceptor 是一个方法级的权限过滤器,基本位于过滤链的最底部

②ExceptionTranslationInterceptor 异常过滤器

③UsernamePasswordAuthenticationFilter 对/login的POST请求做拦截,校验表单中的用户名,密码

 

自定义逻辑 UserDetailService  PasswordEncoder 两个接口

 

①UserDetailService 查数据库

创建类继承UsernamePasswordAuthenticationFilter 重写三个方法

UserDetailService 编写查数据库过程 返回User对象,这个User是SpringSecurity框架定义的对象

②PasswordEncoder 密码加密接口,用于返回User对象里面密码加密

 

web权限方案

(1)认证 (2)授权

①设置用户名和密码:

1)可以通过配置文件设置: spring.security.user.name = " "  spring.security.user.password = " "

2)可以通过配置类实现:

@Configuration

public class SecurityConfig extends WebSecurityConfigurerAdapter{}

重写 configure(AuthenticationManagerBuilder auth)这个方法

String password=passwordEncoder.encode("{password}")

auth.inMemoryAuthentication().withUser("").password("").roles("");

 

@Bean

PasswordEncoder password(){

  return new BCryptPasswordEncoder();

}

3)自定义实现类实现UserDetailService:

第一步 创建一个配置类,设置使用哪个UserDetailService实现类

第二步 编写接口实现类,返回User对象,User对象有用户名密码和操作权限

 1 @Configuration
 2 public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
 3     @Autowired
 4     private UserDetailService userDetailServise;
 5     @Override
 6     protected void configure(AuthenticationManagerBuilder auth)throws Exception {
 7         auth.userDetailsService(userDetailsService).passwordEncoder(password());
 8   }
 9     @Bean
10     PasswordEncoder password(){
11         return new BCryptPasswordEncoder();
12     }
13 }

自定义一个实现类

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException{
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("roles");
       return new User("{username}","{password}",auths);
  }
}

 自定义登录实现 前后端分离

SpringSecurity自己就配置好了登录和登出访问的路径 \login \logout 我们也可以在配置类中(HttpSecurity http)中重新配置访问路径

步骤一:

定义一个UserDetialsService的实现类 UserDetialsServiceImpl()

 

public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    UserService userService;
    @Autowired
    AuthService authService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if(username==null || "".equals(username)){
            throw new RuntimeException("用户名不能为空");
        }
//        根据用户名查用户
        try{
            com.kanno.kqclas.entity.User user = userService.findUserByName(username);
            List<GrantedAuthority> auths = new ArrayList<>();
            List<Auth> authList = authService.findAuthByRoleId(user.getRid());
            for(Auth auth:authList){
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(auth.getId()+"");
                auths.add(grantedAuthority);

            }
            return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auths);
        }catch (Exception e){
            return null;
        }
    }
}

注意,因为SpringSecurity默认返回的是一个User类,所以数据库定义实体类entity的时候最好不要叫User了,否则登录的时候会麻烦

这里返回的User可以配置的不仅仅是用户名和密码,还可以是登录时间、用户状态等,Security会进行更多判断,具体可以查阅官方文档。密码Password一定要经过Encoder,还要和主配置类的Encoder方式一致,

本文这里采用了BCryptEncoder()是一种强校验哈希加密。如果两种加密方式不对,就算是密码一致也无法进行登录。

前端如果使用axios传参,传参默认是把参数放置在请求体当中的,SpringSecurity登录需要在URL中放置参数,可以通过写模板URL来请求。

步骤二:编写SpringSecurity配置类

  @Bean
    public UserDetailsService userDetailsService(){
        return new UserDetailsServiceImpl();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService());
    }
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

指定UserDetailServiceImpl实现类,注入到Bean中,如果使用Autowired,则实现类会使用Security默认验证方式。就不会去查数据库了。指定权限认证办法和加密方式。

步骤三:配置configure方法,指定登录成功、失败方法,配置跨域等。

@Override
    protected void configure(HttpSecurity http) throws Exception {
        //权限校验示例
        http.authorizeRequests().antMatchers("/menu").hasAnyAuthority("menu");
        //登录请求允许所有权限访问
        http.formLogin().permitAll();
        //登录验证成功处理函数
        http.formLogin().successHandler((request,response,ex)->{
            Json json = new Json();
            //        验证用户名密码通过,则颁发一个token令牌 token长度为20
            String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            Random random=new Random();
            StringBuffer sb=new StringBuffer();
            for(int i=0;i<20;i++){
                int number=random.nextInt(62);
                sb.append(str.charAt(number));
            }
//        保存随机生成的token,保存到redis或者数据库中(最好是redis)
            String token = sb.toString();
            String username=request.getParameter("username");
            System.out.println(username);
            try{
                User user = userService.findUserByName(username);
                user.setToken(token);
                Map<String,Object> data = new HashMap<String,Object>();
                data.put("id",user.getId()+"");
                data.put("rid",user.getRid()+"");
                data.put("username",user.getUsername());
                data.put("mobile",user.getMobile());
                data.put("email",user.getEmail());
                data.put("token",token);
                userService.loadToken(user);

                json.setData(data);
                json.setMeta("登录成功!",200);
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                out.write(new ObjectMapper().writeValueAsString(json));
                out.flush();
                out.close();
            }catch (Exception e){
                e.printStackTrace();
            }

        });
//        登录验证失败方法
        http.formLogin().failureHandler((request,response,ex)->{
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            PrintWriter out = response.getWriter();
            Json json = new Json();
            json.setMeta("登录失败!",403);
            out.write(new ObjectMapper().writeValueAsString(json));
            out.flush();
            out.close();
        });
        http.cors(); //开启跨域访问
        http.csrf().disable(); //放行所有postman测试请求

    }

其中跨域请求使用http.cors 开启,需要配置一个名为corsFilter的Bean或者corsConfigurationSource的Bean,来配置跨域请求,这里单独使用cors.disable()不知道为什么没有用...

单独配置一个CorsConfig的@Configuration类

 

@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        configuration.setAllowedOriginPatterns(Collections.singletonList("*"));
        configuration.setAllowedMethods(Collections.singletonList("*"));
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

注意此处查看SpringSecurity的源码:

 

 这里会查找@Bean(name="corsFilter")和@Bean(name="corsConfigurationSource")的配置类,当然Spring默认按类注入,不过最好还是按照这个名字写入。

配置好跨域就可以在前端进行登录认证操作了。

这里还引用了Jackson包,使用ObjectMapper进行写入写出,因为Security我们不需要单独写一个RestController了,返回的JSON格式的数据要用ObjectMapper写。

 

 

posted on 2021-06-01 11:10  KannoRi  阅读(106)  评论(0)    收藏  举报