Spring Security——入门程序(一)

x33g5p2x  于2021-11-13 转载在 Spring  
字(12.7k)|赞(0)|评价(0)|浏览(487)

源码地址:https://gitee.com/huanglei1111/spring-security/tree/master/hl01-spring-security

Spring Security——入门程序(一)

一、spring security 简介

spring security 的核心功能主要包括:

  • 认证 (你是谁)
  • 授权 (你能干什么)
  • 攻击防护 (防止伪造身份)

其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。

注意:绿色的过滤器可以配置是否生效,其他的都不能控制。

比如,对于username password认证过滤器来说,

  • 会检查是否是一个登录请求;
  • 是否包含username 和 password (也就是该过滤器需要的一些认证信息) ;
  • 如果不满足则放行给下一个。

下一个按照自身职责判定是否是自身需要的信息,basic的特征就是在请求头中有 Authorization:Basic eHh4Onh4 的信息。中间可能还有更多的认证过滤器。最后一环是 FilterSecurityInterceptor,这里会判定该请求是否能进行访问rest服务,判断的依据是 BrowserSecurityConfig中的配置,如果被拒绝了就会抛出不同的异常(根据具体的原因)。Exception Translation Filter 会捕获抛出的错误,然后根据不同的认证方式进行信息的返回提示。

二、入门程序

1.导入依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-security</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.mybatis.spring.boot</groupId>
  8. <artifactId>mybatis-spring-boot-starter</artifactId>
  9. <version>1.3.1</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>mysql</groupId>
  13. <artifactId>mysql-connector-java</artifactId>
  14. <version>5.1.47</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-web</artifactId>
  23. </dependency>
  24. </dependencies>

2.创建数据库

一般权限控制有三层,即:用户<–>角色<–>权限,用户与角色是多对多,角色和权限也是多对多。这里我们先暂时不考虑权限,只考虑用户<–>角色

创建用户表sys_user

  1. CREATE TABLE `sys_user` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `name` varchar(255) NOT NULL,
  4. `password` varchar(255) NOT NULL,
  5. PRIMARY KEY (`id`)
  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建权限表sys_role

  1. CREATE TABLE `sys_role` (
  2. `id` int(11) NOT NULL,
  3. `name` varchar(255) NOT NULL,
  4. PRIMARY KEY (`id`)
  5. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建用户-角色表sys_user_role

  1. CREATE TABLE `sys_user_role` (
  2. `user_id` int(11) NOT NULL,
  3. `role_id` int(11) NOT NULL,
  4. PRIMARY KEY (`user_id`,`role_id`),
  5. KEY `fk_role_id` (`role_id`),
  6. CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  7. CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

初始化一下数据:

  1. INSERT INTO `sys_role` VALUES ('1', 'ROLE_ADMIN');
  2. INSERT INTO `sys_role` VALUES ('2', 'ROLE_USER');
  3. INSERT INTO `sys_user` VALUES ('1', 'admin', '123');
  4. INSERT INTO `sys_user` VALUES ('2', 'jitwxs', '123');
  5. INSERT INTO `sys_user_role` VALUES ('1', '1');
  6. INSERT INTO `sys_user_role` VALUES ('2', '2');

这里的权限格式为ROLE_XXX,是Spring Security规定的,不要乱起名字哦。

3.准备页面

用于登陆的login.html以及用于登陆成功后的home.html,将其放置在 resources/templates 目录下:

(1)login.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>登陆</title>
  6. </head>
  7. <body>
  8. <h1>登陆</h1>
  9. <form method="post" action="/login">
  10. <div>
  11. 用户名:<input type="text" name="username">
  12. </div>
  13. <div>
  14. 密码:<input type="password" name="password">
  15. </div>
  16. <div>
  17. <button type="submit">立即登陆</button>
  18. </div>
  19. </form>
  20. </body>
  21. </html>

用户的登陆认证是由Spring Security进行处理的,请求路径默认为/login,用户名字段默认为username,密码字段默认为password

(2)home.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <h1>登陆成功</h1>
  9. <a href="/admin">检测ROLE_ADMIN角色</a>
  10. <a href="/user">检测ROLE_USER角色</a>
  11. <button onclick="window.location.href='/logout'">退出登录</button>
  12. </body>
  13. </html>

4.配置application.yml

  1. spring:
  2. datasource:
  3. driver-class-name: com.mysql.jdbc.Driver
  4. url: jdbc:mysql://localhost:3306/security_test?useUnicode=true&characterEncoding=utf-8&useSSL=false
  5. username: root
  6. password: root
  7. thymeleaf:
  8. cache: false # 开发配置为false,避免修改模板还要重启服务器
  9. mode: HTML5 #模板的模式,支持HTML XML TEXT JAVASCRIPT
  10. encoding: UTF-8
  11. prefix: classpath:/templates/
  12. mybatis:
  13. configuration:
  14. map-underscore-to-camel-case: true

5.创建实体类,mapper,service和controller

实体类

SysRole.java

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class SysRole implements Serializable {
  5. static final long serialVersionUID = 1L;
  6. private Integer id;
  7. private String name;
  8. }

SysUser.java

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class SysUser implements Serializable {
  5. static final long serialVersionUID = 1L;
  6. private Integer id;
  7. private String name;
  8. private String password;
  9. }

SysUserRole.java

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class SysUserRole implements Serializable {
  5. static final long serialVersionUID = 1L;
  6. private Integer userId;
  7. private Integer roleId;
  8. }
mapper

SysRoleMapper.java

  1. @Mapper
  2. public interface SysRoleMapper {
  3. @Select("SELECT * FROM sys_role WHERE id = #{id}")
  4. SysRole selectById(Integer id);
  5. }

SysUserMapper.java

  1. @Mapper
  2. public interface SysUserMapper {
  3. @Select("SELECT * FROM sys_user WHERE id = #{id}")
  4. SysUser selectById(Integer id);
  5. @Select("SELECT * FROM sys_user WHERE name = #{name}")
  6. SysUser selectByName(String name);
  7. }

SysUserRoleMapper.java

  1. @Mapper
  2. public interface SysUserRoleMapper {
  3. @Select("SELECT * FROM sys_user_role WHERE user_id = #{userId}")
  4. List<SysUserRole> listByUserId(Integer userId);
  5. }
Service

SysRoleService.java

  1. @Service
  2. public class SysRoleService {
  3. @Autowired
  4. private SysRoleMapper roleMapper;
  5. public SysRole selectById(Integer id){
  6. return roleMapper.selectById(id);
  7. }
  8. }

SysUserService.java

  1. @Service
  2. public class SysUserService {
  3. @Autowired
  4. private SysUserMapper userMapper;
  5. public SysUser selectById(Integer id) {
  6. return userMapper.selectById(id);
  7. }
  8. public SysUser selectByName(String name) {
  9. return userMapper.selectByName(name);
  10. }
  11. }

SysUserRoleService.java

  1. @Service
  2. public class SysUserRoleService {
  3. @Autowired
  4. private SysUserRoleMapper userRoleMapper;
  5. public List<SysUserRole> listByUserId(Integer userId) {
  6. return userRoleMapper.listByUserId(userId);
  7. }
  8. }
controller
  1. @Controller
  2. public class LoginController {
  3. private final Logger logger = LoggerFactory.getLogger(LoginController.class);
  4. @RequestMapping("/")
  5. public String showHome() {
  6. String name = SecurityContextHolder.getContext().getAuthentication().getName();
  7. logger.info("当前登陆用户:" + name);
  8. return "home.html";
  9. }
  10. @RequestMapping("/login")
  11. public String showLogin() {
  12. return "login.html";
  13. }
  14. @RequestMapping("/admin")
  15. @ResponseBody
  16. @PreAuthorize("hasRole('ROLE_ADMIN')")
  17. public String printAdmin() {
  18. return "如果你看见这句话,说明你有ROLE_ADMIN角色";
  19. }
  20. @RequestMapping("/user")
  21. @ResponseBody
  22. @PreAuthorize("hasRole('ROLE_USER')")
  23. public String printUser() {
  24. return "如果你看见这句话,说明你有ROLE_USER角色";
  25. }
  26. }
  • @PreAuthorize 用于判断用户是否有指定权限,没有就不能访问
  • 如代码所示,获取当前登录用户:SecurityContextHolder.getContext().getAuthentication()

6.配置Spring Security

UserDetailsService

首先我们需要自定义 UserDetailsService ,将用户信息和权限注入进来。

我们需要重写loadUserByUsername 方法,参数是用户输入的用户名。返回值是UserDetails,这是一个接口,一般使用它的子类org.springframework.security.core.userdetails.User,它有三个参数,分别是用户名、密码和权限集。
实际情况下,大多将 DAO 中的 User 类继承 org.springframework.security.core.userdetails.User 返回。

  1. package com.hl.hl01springsecurity.security;
  2. import com.hl.hl01springsecurity.entity.SysRole;
  3. import com.hl.hl01springsecurity.entity.SysUser;
  4. import com.hl.hl01springsecurity.entity.SysUserRole;
  5. import com.hl.hl01springsecurity.service.SysRoleService;
  6. import com.hl.hl01springsecurity.service.SysUserRoleService;
  7. import com.hl.hl01springsecurity.service.SysUserService;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.security.core.GrantedAuthority;
  10. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  11. import org.springframework.security.core.userdetails.User;
  12. import org.springframework.security.core.userdetails.UserDetails;
  13. import org.springframework.security.core.userdetails.UserDetailsService;
  14. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  15. import org.springframework.stereotype.Service;
  16. import java.util.ArrayList;
  17. import java.util.Collection;
  18. import java.util.List;
  19. @Service("userDetailsService")
  20. public class CustomUserDetailsServiceImpl implements UserDetailsService {
  21. @Autowired
  22. private SysUserService userService;
  23. @Autowired
  24. private SysRoleService roleService;
  25. @Autowired
  26. private SysUserRoleService userRoleService;
  27. @Override
  28. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  29. Collection<GrantedAuthority> authorities = new ArrayList<>();
  30. // 从数据库中取出用户信息
  31. SysUser user = userService.selectByName(username);
  32. // 判断用户是否存在
  33. if(user == null) {
  34. throw new UsernameNotFoundException("用户名不存在");
  35. }
  36. // 添加权限
  37. List<SysUserRole> userRoles = userRoleService.listByUserId(user.getId());
  38. for (SysUserRole userRole : userRoles) {
  39. SysRole role = roleService.selectById(userRole.getRoleId());
  40. authorities.add(new SimpleGrantedAuthority(role.getName()));
  41. }
  42. // 返回UserDetails实现类
  43. return new User(user.getName(), user.getPassword(), authorities);
  44. }
  45. }
WebSecurityConfig

该类是 Spring Security 的配置类,该类的三个注解分别是标识该类是配置类、开启 Security 服务、开启全局 Securtiy 注解。

首先将我们自定义的 userDetailsService 注入进来,在 configure() 方法中使用 auth.userDetailsService() 方法替换掉默认的 userDetailsService。

这里我们还指定了密码的加密方式(5.0 版本强制要求设置),因为我们数据库是明文存储的,所以明文返回即可,如下所示:

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  4. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  5. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  6. import org.springframework.security.config.annotation.web.builders.WebSecurity;
  7. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  8. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  9. import org.springframework.security.crypto.password.PasswordEncoder;
  10. @Configuration
  11. @EnableWebSecurity //开启security服务
  12. @EnableGlobalMethodSecurity(prePostEnabled = true)//开启全局security注解
  13. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  14. @Autowired
  15. private CustomUserDetailsServiceImpl userDetailsService;
  16. @Override
  17. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  18. //使用下面的方法替换掉默认的userDetailsService
  19. auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {
  20. @Override
  21. public String encode(CharSequence charSequence) {
  22. return charSequence.toString();
  23. }
  24. @Override
  25. public boolean matches(CharSequence charSequence, String s) {
  26. return s.equals(charSequence.toString());
  27. }
  28. });
  29. }
  30. @Override
  31. protected void configure(HttpSecurity http) throws Exception {
  32. http.authorizeRequests()
  33. // 如果有允许匿名的url,填在下面
  34. // .antMatchers().permitAll()
  35. .anyRequest().authenticated()
  36. .and()
  37. // 设置登陆页
  38. .formLogin().loginPage("/login")
  39. // 设置登陆成功页
  40. .defaultSuccessUrl("/").permitAll()
  41. // 自定义登陆用户名和密码参数,默认为username和password
  42. // .usernameParameter("username")
  43. // .passwordParameter("password")
  44. .and()
  45. .logout().permitAll();
  46. // 关闭CSRF跨域
  47. http.csrf().disable();
  48. }
  49. @Override
  50. public void configure(WebSecurity web) throws Exception {
  51. // 设置拦截忽略文件夹,可以对静态资源放行
  52. web.ignoring().antMatchers("/css/**", "/js/**");
  53. }
  54. }

注:如果你想要将密码加密,可以修改 configure() 方法如下:

  1. @Override
  2. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  3. auth.userDetailsService(userDetailsService)
  4. .passwordEncoder(new BCryptPasswordEncoder());
  5. }

7.测试

  1. ROLE_ADMIN 账户:用户名 admin,密码 123
  2. ROLE_USER 账户:用户名 jitwxs,密码 123

这里是以admin用户进行登录的,点击上面蓝色的role_admin会看到上面的话,如果是以user用户登录的就会恰恰相反。

三、final类httpSecurity

方法说明
openidLogin()用于基于 OpenId 的验证
headers()将安全标头添加到响应
cors()配置跨域资源共享( CORS )
sessionManagement()允许配置会话管理
portMapper()允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee()配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理
x509()配置基于x509的认证
rememberMe允许配置“记住我”的验证
authorizeRequests()允许基于使用HttpServletRequest限制访问
requestCache()允许配置请求缓存
exceptionHandling()允许配置错误处理
securityContext()HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
servletApi()HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
csrf()添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
logout()添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”
anonymous()允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin()指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
oauth2Login()根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
requiresChannel()配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
httpBasic()配置 Http Basic 验证
addFilterAt()在指定的Filter类的位置添加过滤器

相关文章