查看: 288|回复: 0

[Java语言] Spring Boot整合Spring Security的示例代码

发表于 2018-5-5 09:30:44

本文讲述Spring Boot整合Spring Security在方法上使用注解实现权限控制,使用自定义UserDetailService,从MySQL中加载用户信息。使用Security自带的MD5加密,对用户密码进行加密。页面模板采用thymeleaf引擎。

源码地址:https://github.com/li5454yong/springboot-security.git

1、引入pom依赖

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>1.4.4.RELEASE</version>
  5. </parent>
  6. <dependencies>
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-web</artifactId>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-security</artifactId>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.springframework.security.oauth</groupId>
  17. <artifactId>spring-security-oauth2</artifactId>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-data-jpa</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-jdbc</artifactId>
  30. </dependency>
  31. <dependency>
  32. <groupId>mysql</groupId>
  33. <artifactId>mysql-connector-java</artifactId>
  34. <version>5.1.34</version>
  35. </dependency>
  36. <dependency>
  37. <groupId>com.alibaba</groupId>
  38. <artifactId>druid</artifactId>
  39. <version>1.0.15</version>
  40. </dependency>
  41. </dependencies>
复制代码

这里使用druid连接池,Spring Data Jpa实现数据库访问。

2、配置Spring Security

  1. @Configuration
  2. @EnableWebMvcSecurity
  3. @EnableGlobalMethodSecurity(prePostEnabled = true)//开启security注解
  4. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  5. @Bean
  6. @Override
  7. protected AuthenticationManager authenticationManager() throws Exception {
  8. return super.authenticationManager();
  9. }
  10. @Override
  11. protected void configure(HttpSecurity http) throws Exception {
  12. //允许所有用户访问"/"和"/home"
  13. http.authorizeRequests()
  14. .antMatchers("/", "/home").permitAll()
  15. //其他地址的访问均需验证权限
  16. .anyRequest().authenticated()
  17. .and()
  18. .formLogin()
  19. //指定登录页是"/login"
  20. .loginPage("/login")
  21. .defaultSuccessUrl("/hello")//登录成功后默认跳转到"/hello"
  22. .permitAll()
  23. .and()
  24. .logout()
  25. .logoutSuccessUrl("/home")//退出登录后的默认url是"/home"
  26. .permitAll();
  27. }
  28. @Autowired
  29. public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  30. auth
  31. .userDetailsService(customUserDetailsService())
  32. .passwordEncoder(passwordEncoder());
  33. }
  34. /**
  35. * 设置用户密码的加密方式为MD5加密
  36. * @return
  37. */
  38. @Bean
  39. public Md5PasswordEncoder passwordEncoder() {
  40. return new Md5PasswordEncoder();
  41. }
  42. /**
  43. * 自定义UserDetailsService,从数据库中读取用户信息
  44. * @return
  45. */
  46. @Bean
  47. public CustomUserDetailsService customUserDetailsService(){
  48. return new CustomUserDetailsService();
  49. }
  50. }
复制代码

这里只做了基本的配置,设置了登录url、登录成功后跳转的url、退出后跳转到的url。使用@EnableGlobalMethodSecurity(prePostEnabled = true)这个注解,可以开启security的注解,我们可以在需要控制权限的方法上面使用@PreAuthorize,@PreFilter这些注解。

3、自定义userDetailService

  1. public class CustomUserDetailsService implements UserDetailsService {
  2. @Autowired //数据库服务类
  3. private SUserService suserService;
  4. @Override
  5. public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
  6. //SUser对应数据库中的用户表,是最终存储用户和密码的表,可自定义
  7. //本例使用SUser中的email作为用户名:
  8. SUser user = suserService.findUserByEmail(userName);
  9. if (user == null) {
  10. throw new UsernameNotFoundException("UserName " + userName + " not found");
  11. }
  12. // SecurityUser实现UserDetails并将SUser的Email映射为username
  13. SecurityUser securityUser = new SecurityUser(user);
  14. Collection<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
  15. authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
  16. return securityUser;
  17. }
  18. }
复制代码

这里只需要实现UserDetailsService 接口,重写loadUserByUsername方法,从数据库中取出用户信息。最后返回一个UserDetails 实现类。

4、定义错误处理配置

  1. @Configuration
  2. public class ErrorPageConfig {
  3. @Bean
  4. public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
  5. return new MyCustomizer();
  6. }
  7. private static class MyCustomizer implements EmbeddedServletContainerCustomizer {
  8. @Override
  9. public void customize(ConfigurableEmbeddedServletContainer container) {
  10. container.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN, "/403"));
  11. }
  12. }
  13. }
复制代码

访问发生错误时,跳转到”/403”.

5、Controller接口

  1. @Controller
  2. public class IndexController {
  3. @Resource
  4. private SUserService sUserService;
  5. @RequestMapping("/home")
  6. public String home() {
  7. return "home";
  8. }
  9. @PreAuthorize("hasRole('user')")
  10. @RequestMapping(value = "/admin",method = RequestMethod.GET)
  11. public String toAdmin(){
  12. return "helloAdmin";
  13. }
  14. @RequestMapping("/hello")
  15. public String hello() {
  16. return "hello";
  17. }
  18. @RequestMapping("/login")
  19. public String login(){
  20. return "login";
  21. }
  22. @RequestMapping("/")
  23. public String root() {
  24. return "index";
  25. }
  26. @RequestMapping("/403")
  27. public String error(){
  28. return "403";
  29. }
  30. }
复制代码

在toAdmin()方法上面使用了@PreAuthorize(“hasRole(‘user')”),表示访问这个方法需要拥有user角色。如果希望控制到权限层面,可以使用@PreAuthorize(“hasPermission()”)。这里只是用了其中的一个用法,更多的使用方法可以去看官方文档。需要注意的是,Spring Security默认的角色前缀是”ROLE_”,使用hasRole方法时已经默认加上了,因此我们在数据库里面的用户角色应该是“ROLE_user”,在user前面加上”ROLE_”前缀。

6、测试

启动项目,访问http://localhost:1130/login

点击登录后进入到“/hello”

点击跳转到管理员页面

在后台的“/admin”这个url对应的方法上面,限制了用户必须要拥有“user”角色。在数据库中也设置了登录的用户有这个角色。
现在我们修改数据库中的用户角色,改为“ROLE_admin”。退出登录后重新登录,再次点击“前往管理员页面”按钮,会跳转到如下页面。


因为现在没有了“user”权限,所以访问的时候抛出了异常,被拦截后重定向到了“/403”。

7、POST方式访问,错误码403

首先,把“/admin”改为POST请求

  1. @PreAuthorize("hasRole('user')")
  2. @RequestMapping(value = "/admin",method = RequestMethod.POST)
  3. public String toAdmin(){
  4. return "helloAdmin";
  5. }
复制代码

把“前往管理员页面”按钮的请求方式从原来的form表达get提交,改为ajax方式POST提交。至于为什么不是用form的POST提交后面再讲。先修改代码

  1. <body>
  2. <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
  3. <!--<form th:action="@{/logout}" method="post">
  4. <input type="submit" value="Sign Out"/>
  5. </form>
  6. <form th:action="@{/admin}" method="get">
  7. <input th:type="submit" th:value="前往管理员页面"/>
  8. </form>-->
  9. <a th:href="@{/admin}" rel="external nofollow" >前往管理员用户页面</a>
  10. <input th:type="submit" onclick="testPost()" th:value="前往管理员页面"/>
  11. </body>
  12. <script>
  13. function testPost() {
  14. $.ajax({
  15. url:"/admin",
  16. type:'POST',
  17. success:function (data) {
  18. }
  19. });
  20. }
  21. </script>
复制代码

点击“前往管理员页面”按钮,在调试台可以看到如下


这是因为框架内部防止CSRF(Cross-site request forgery跨站请求伪造)的发生,限制了除了get以外的大多数方法。

下面说解决办法:

首先在标签内添加如下内容。

  1. <meta name="_csrf" th:content="${_csrf.token}"/>
  2. <meta name="_csrf_hader" th:content="${_csrf.headerName}"/>
复制代码

只要添加这个token,后台就会验证这个token的正确性,如果正确,则接受post访问。
然后在ajax代码中添加以下代码:

  1. var token = $('meta[name="_csrf"]').attr("content");
  2. var header = $('meta[name="_csrf_hader"]').attr("content");
  3. $(document).ajaxSend(function(e,xhr,opt){
  4. xhr.setRequestHeader(header,token);
  5. });
复制代码

这样就可以正常使用POST、DELETE等其他方式来访问了。

上面说到使用表单的POST方式来提交,通过查看页面源代码可以看到


框架在form表单中自动插入了一个隐藏域,value值就是那个token,所以使用form表单来提交POST请求是可以直接通过的,而ajax方式提交的话,需要加上那段代码。

好了,这篇文章就讲到这,后面还会有文章讲述REST API风格如何来使用Spring Security来控制权限。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持程序员之家。



回复

使用道具 举报