@
目录
本教程是基于SpringMVC而创建的,不适用于WebFlux。(如果你不知道这两者,可以忽略这句提示)
所有的技术是为了解决实际问题而出现的,所以我们并不空谈,也不去讲那么多的概念。在这样一个系统中,有三个接口,需要授权给三种权限的人使用,如下表:
接口地址 | 需要的权限描述 | 可访问的权限组名称 |
---|---|---|
visitor/main | 不需要权限,也不用登录,谁都可以访问 | |
admin/main | 必须登录,只有管理员可以访问 | ADMIN |
user/main | 必须登录,管理员和用户权限都能访问 | USER和ADMIN |
在Controller中判断用户是否登录和用户的权限组判断是否可以访问
这是最不现实的解决方案,可是我刚进公司时的项目就是这样设计的,当时我还觉得很高大尚呢。
使用Web应用的三大组件中和过滤器(Filter)进行判断
这是正解,SpringSecurity也正是用的这个原理。如果你的项目足够简单,建议你直接使用这种方式就可以了,并不需要集成SpringSecurity。这部分的示例在代码中有演示,自己下载代码查看即可。
我们可以直接使用SpringSecurity框架来解决这个问题
网上的教程那么多,但是讲的都不清不楚。所以,请仔细阅读下段这些话,这要比后边的代码重要。
SpringSecurity主要有两部分内容:
这里的登录是对于浏览器访问来说的,因为如果是前后端分离时,使用的是Token进行授权的,也可以理解为登录用户,这个后边会讲。这里只是为了知识的严谨性才提到了这点
<!-- 不用写版本,继承Springboot的版本--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
是支持使用xml进行配置的,但是在SpringBoot中更建议使用Java注解配置
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 配置用户权限组和接口路径的关系 * 和一些其他配置 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 对请求进行验证 .antMatchers("/visitor/**").permitAll() .antMatchers("/admin/**").hasRole("ROLE_ADMIN") // 必须有ADMIN权限 .antMatchers("/user/**").hasAnyRole("ROLE_USER", "ROLE_ADMIN") //有任意一种权限 .anyRequest() //任意请求(这里主要指方法) .authenticated() //// 需要身份认证 .and() //表示一个配置的结束 .formLogin().permitAll() //开启SpringSecurity内置的表单登录,会提供一个/login接口 .and() .logout().permitAll() //开启SpringSecurity内置的退出登录,会为我们提供一个/logout接口 .and() .csrf().disable(); //关闭csrf跨站伪造请求 } }
上边的配置主要内容有两个:
说明:
配置了上边的接口和用户权限角色的关系后,就是要配置我们的用户名和密码了。如果没有正确的用户名和密码,神仙也登录不上去。
关于这个,网上的教程有各种各样的配置,其实就一个接口,我们只需要实现这个接口中的方法就可以了。接口代码如下:
package org.springframework.security.core.userdetails; public interface UserDetailsService { /** * 在登录的时候,就会调用这个方法,它的返回结果是一个UserDetails接口类 */ UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException; }
来看一下这个接口,如果想扩展,可以自己写一个实现类,也可以使用SpringSecurity提供的实现
public interface UserDetails extends Serializable { // 用户授权集合 Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }
UserDetailsServicer接口的实现类
@Configuration public class UserDetailsServiceImpl implements UserDetailsService { /** * 这个方法要返回一个UserDetails对象 * 其中包括用户名,密码,授权信息等 * * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { /** * 将我们的登录逻辑写在这里 * 我是直接在这里写的死代码,其实应该从数据库中根据用户名去查 */ if (username == null) { //返回null时,后边就会抛出异常,就会登录失败。但这个异常并不需要我们处理 return null; } if (username.equals("lyn4ever")) { //这是构造用户权限组的代码 //但是这个权限上加了ROLE_前缀,而在之前的配置上却没有加。 //与其说这不好理解,倒不如说这是他设计上的一个小缺陷 SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER"); List<SimpleGrantedAuthority> list = new ArrayList<>(); list.add(authority); //这个user是UserDetails的一个实现类 //用户密码实际是lyn4ever,前边加{noop}是不让SpringSecurity对密码进行加密,使用明文和输入的登录密码比较 //如果不写{noop},它就会将表表单密码进行加密,然后和这个对比 User user = new User("lyn4ever", "{noop}lyn4ever", list); return user; } if (username.equals("admin")) { SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER"); SimpleGrantedAuthority authority1 = new SimpleGrantedAuthority("ROLE_ADMIN"); List<SimpleGrantedAuthority> list = new ArrayList<>(); list.add(authority); list.add(authority1); User user = new User("admiin", "{noop}admin", list); return user; } //其他返回null return null; } }
分别访问上边三个接口,可以看到访问结果和上边的流程是一样的。
代码地址