使用Spring Security

90 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

文章目录


#使用Spring Security

简介

spring security是一个提供声明式的安全访问控制解决方案的安全框架,为应用系统提供声明式的安全访问功能,减少了为企业系统安全控制编写大量重复代码的工作。
其中,spring security提供了一下功能:
身份认证
授权
加密
会话管理
Session管理
支持HTTP/HTTPS
支持Basic和Digest认证
Remember-Me
CORS
等等等等…
下面是用spring security做简单的登录检测与权限控制

场景及初始化数据

数据库有user,role,menu三个实体,其中两两之间都是多对多的关系。而根据关系数据库设计原理,多对多关系会衍生一个新表,所以,表结构大致如下
图片一

依赖与配置文件点此查看
源码点此查看

@ManytoMany的使用

    @ManyToMany(fetch= FetchType.EAGER)
    @JoinTable(name = "UserRole", joinColumns = { @JoinColumn(name = "userId") },
    inverseJoinColumns ={@JoinColumn(name = "roleId") })
    private List<Role> roleList;

其中(fetch= FetchType.EAGER)的使用需要注意,两边只需要写一个,而不是两变都写,负责就会报错

字段的大小写

使用jpa,若有个属性为password,那么表中就是password,若表中是pass_word,那么就会报错,必须吧属性改成passWord才对。。。这个实在太坑了

MyUserDetailsService

使用数据库认证需要自定义一个类来实现UserDetailsService重写loadUserByUsername方法进行认证授权
也就是说,如果需要结合自己的数据库来进行认证,那么就必须自定义自己的DetailService,并实现loadUserByUsername。
类名前必须加上@Service注释
GrantedAuthority表示已授予的权限

    //username为从前端页面接收到的数据,然后使用jpa查询数据库进行验证

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUserName(username);
        System.out.println(user.getPassWord());
        System.out.println(user.getUserName());
        if (user == null){
            throw new UsernameNotFoundException("用户不存在!");
        }

        //再通过SimpleGrantedAuthority聚合该用户的所有role

        List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
        for (Role role : user.getRoleList()) {
            simpleGrantedAuthorities.add(new SimpleGrantedAuthority(role.getRoleName()));
        }

        //然后返回
        return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassWord(), simpleGrantedAuthorities);
    }

SecurityConfig

SecurityConfig用来进行相关配置
首先配置资源文件,哪些可以访问,哪些需要有权限访问

        //配置资源文件 其中/css/**,/index可以任意访问,/select需要USER权限,/delete需要ADMIN权限
        httpSecurity
                .authorizeRequests()
                .antMatchers("/css/**", "/index").permitAll()
                .antMatchers("/select").hasRole("USER")
                .antMatchers("/delete").hasRole("ADMIN");

接着聚合数据库中的角色权限

        //动态加载数据库中角色权限
        List<Role> roleList = roleRepository.findAll();
        for(Role role : roleList){
            List<Menu> menuList = role.getMenuList();
            for (Menu menu : menuList){
                //在SpringSecurity校验权限的时候,会自动将权限前面加ROLE_,所以我们需要 将我们数据库中配置的ROLE_截取掉。
                String roleName = role.getRoleName().replace("ROLE_","");
                String menuName = "/" + menu.getMenuName();
                httpSecurity
                        .authorizeRequests()
                        .antMatchers(menuName)
                        .hasRole(roleName);
            }
        }

然后配置登录请求,登录异常与注销登录等操作

       //配置登录请求/login 登录失败请求/login_error 登录成功请求/
        httpSecurity
                .formLogin()
                .loginPage("/login")
                .failureUrl("/login_error")
                .successForwardUrl("/");
        //登录异常,如权限不符合 请求/401
        httpSecurity
                .exceptionHandling().accessDeniedPage("/401");
        //注销登录 请求/logout
        httpSecurity
                .logout()
                .logoutSuccessUrl("/logout");
    }

NoOpPasswordEncoder设置密码不加密

    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

configureGlobal根据自己自定义的登录证明或者在内存中加入用户来实现登录
AuthenticationManagerBuilder允许轻松构建内存身份验证
userDetailsService使用自定义的登录证明

    //根据用户名密码实现登录
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .inMemoryAuthentication()
                //.passwordEncoder(new BCryptPasswordEncoder())
                .withUser("test").password("123").roles("USER")
                .and()
                .withUser("admin").password("123").roles("ADMIN","USER");
        authenticationManagerBuilder.userDetailsService(myUserDetailsService);
    }

@Controller和@RestController的区别

@RestController注解相当于@ResponseBody + @Controller合在一起的作用。

  1. 如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器 InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。
  2. 如果需要返回到指定页面,则需要用 @Controller配合视图解析器InternalResourceViewResolver才行。
    InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。
  3. 如果需要返回到指定页面,则需要用 @Controller配合视图解析器InternalResourceViewResolver才行。
    如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。

总结

一杯茶一包烟,一个小小的bug就能让你改一天,如果不明白过程或者原理,那么总有一个地方,哪怕是照着敲上去,都会出错。