前言 As we all know,现今主流权限框架有 SpringSecurity、Shiro、SaToken,Shiro在前后端分离时代基本被淘汰,剩下适合大型项目的 SpringSecurity
和 适合中小型项目的 SaToken
可以选择,SaToken 我也写了文章 Springboot 使用 SaToken 进行登录认证、权限管理以及路由规则接口拦截
SpringSecurity 作为 Spring 的官方权限框架,肯定是最牛逼的,当然也最复杂,中小型项目还是 SaToken 来的省心呀,简单,几行代码实现认证、拦截、踢人、单点登录等,SpringSecurity 想要实现这些功能,需要深入研究,现在我只写最简单的用户认证和接口权限控制。
现在的 SpringSecurity 版本更换了新的配置方式,下面有写
一、引入依赖
自己新建一个标准的 Springboot web 项目,然后增加下面这个依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency >
二、启动类增加注解
@EnableWebSecurity
表示启用户 springsecurity 功能@EnableGlobalMethodSecurity(prePostEnabled = true)
是开启基于注解的接口权限控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @SpringBootApplication @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SpringSecurityDemoApplication { public static void main (String[] args) { SpringApplication.run(SpringSecurityDemoApplication.class, args); } }
三、config配置文件 现在的 SpringSecurity 版本更换了新的配置方式,目前新版本仍兼容旧版配置,你不喜欢新版配置也可以用旧版
旧版配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 import org.springframework.boot.SpringBootConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.http.HttpMethod;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.builders.WebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;@SpringBootConfiguration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } public void configure (WebSecurity web) throws Exception { web.ignoring().antMatchers("/resources/**" , "/ignore2" ); } @Override protected void configure (HttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeRequests() .antMatchers(HttpMethod.GET, "/test/any" ).permitAll() .antMatchers("/test/admin" ).hasRole("admin" ) .anyRequest().authenticated() .and() .formLogin() .loginProcessingUrl("/login" ) .and() .csrf().disable(); } }
新版配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package icu.xuyijie.springsecuritydemo.config;import org.springframework.boot.SpringBootConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.http.HttpMethod;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.SecurityFilterChain;@SpringBootConfiguration public class WebSecurityNewConfig { @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean public WebSecurityCustomizer webSecurityCustomizer () { return (web) -> web.ignoring().antMatchers("/resources/**" , "/ignore2" ); } @Bean public SecurityFilterChain securityFilterChain (HttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeRequests() .antMatchers(HttpMethod.GET, "/test/any" , "/js/**" , "/css/**" , "/images/**" , "/icon/**" , "/file/**" ).permitAll() .antMatchers("/test/admin" ).hasRole("admin" ) .anyRequest().authenticated() .and() .formLogin() .loginProcessingUrl("/login" ) .and() .csrf().disable(); return httpSecurity.build(); } }
四、UserDetailsServiceImpl
UserDetailsService
是 SpringSecurity 的内置类,我们需要实现它的 loadUserByUsername 方法,方法参数 username
就是登录时填写的用户名,里面写从数据库获取这个 username 的密码和角色,然后 return 给 SpringSecurity 内置的 User 实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;@Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private final PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { String role = "user" ; String password = "123456" ; List<GrantedAuthority> authorityList = new ArrayList <>(); authorityList.add(new SimpleGrantedAuthority ("ROLE_" + role)); return new User ( username, passwordEncoder.encode(password), authorityList ); } }
五、写一个Controller测试用
上面主启动类添加的注解开启基于注解的接口权限的意思就是开启下面的 @PreAuthorize
注解的功能, 这个注解和config
里面配置的 .antMatchers("/test/admin").hasRole("admin")
一个意思,选其中一个方式即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/test") public class TestController { @PreAuthorize("hasAnyRole('user', 'admin')") @RequestMapping("/user") public String user () { System.out.println("user和admin角色访问" ); return "user和admin角色访问" ; } @PreAuthorize("hasAnyRole('admin')") @RequestMapping("/admin") public String admin () { System.out.println("admin角色访问" ); return "admin角色访问" ; } @RequestMapping("/any") public String any () { System.out.println("这个接口Get请求无需登录" ); return "这个接口Get请求无需登录" ; } }
六、启动测试
我们先访问 http://127.0.0.1:8081/test/any
,这时我们还没有登录,这个接口我们在config
里面配置了.permitAll()
,所以没有被拦截,直接访问成功
下面我门访问 http://127.0.0.1:8081/test/user
,发现浏览器自动跳转到了登录界面,这个登录界面是SpringSecurity内置的,如需使用自定义页面,下面会讲
输入账号密码点击 Sign in,发现浏览器自动跳回http://127.0.0.1:8081/test/user
,访问成功 这里我们用户名可以随便输入,因为上面UserDetailsServiceImpl
中我们没有指定用户名,所以 123456 这个密码所有用户都能用
如果访问 /test/admin
这个接口,报错 403,代表无权限
七、前后端分离设计 1、自定义登录界面
config
里面配置的 .loginProcessingUrl("/login")
是默认使用SpringSecurity内置登录页面,如果需要使用前端登陆页面,可以配置一个 MvnConfig 拦截接口,让前端跳转到他们的登录页面,然后把登录请求发送给/login这个内置接口就行了
当然也可以单独写一个登录页面放到后端的 resources/static
里面,这样可以直接在 config的loginProcessingUrl
中修改
2、自定义登录成功/失败处理器
自定义处理器,这里写你登录失败的逻辑,返回给前端数据,让前端进行页面跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Component public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure (HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8" ); System.out.println("登录成功" ); } }
在config
的securityFilterChain
方法里面添加下面代码,loginSuccessHandler
代码和上面一样
1 2 3 .successHandler(loginSuccessHandler) .failureHandler(loginFailureHandler)
总结
SpringSecurity 作为 Spring 的官方权限框架,肯定是最牛逼的,当然也最复杂,中小型项目还是 SaToken 来的省心呀,简单,几行代码实现认证、拦截、踢人、单点登录等,SpringSecurity 想要实现这些功能,SaToken 我也写了文章 Springboot 使用 SaToken 进行登录认证、权限管理以及路由规则接口拦截