Spring Boot 集成Spring Security实现登录认证

800 阅读2分钟

前言

在企业开发中系统的认证和权限控制是常见的需求,前面几篇文章已经介绍了Spring Boot集成Shiro实现权限验证,本文将讲解Spring Boot集成Spring Security如何实现认证和权限控制。

简介

Spring Security是一种基于Spring AOP和过滤器Filte的框架,它提供了供在Web请求和方法调用级别的用户鉴权和权限控制。 Web应用的安全性通常包括:用户认证(Authentication)和用户授权(Authorization)。

  • 用户授权:指的是验证某个用户是否为系统合法用户,也就是说用户能否访问该系统。
  • 用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证。

认证流程图

spring_security_auth.jpg

核心组件

SecurityContextHolder

用于存储应用程序安全上下文(Spring Context)的详细信息,如当前操作的用户对象信息、认证状态、角色权限信息

获取用户信息:

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) 
{
    String username = ((UserDetails)principal).getUsername();
} 
else 
{
    String username = principal.toString();
}

Authentication

认证信息接口,集成了Principal类返回用户和权限信息,该接口提供如下方法:

图片.png

AuthenticationManager

认证管理器负责验证,认证成功后,AuthenticationManager返回用户认证信息(包括权限信息、身份信息、详细信息)的 Authentication 实例。然后再将 Authentication实例设置到 SecurityContextHolder容器中。

UserDetailsService

负责加载用户信息,通过是通过数据库加载实现,也可以通过内存映射InMemoryDaoImpl实现。

UserDetails

用户详细信息,该接口提供了如下方法:

图片.png

集成Spring Security

数据库设计

图片.png

导入jar包

 <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
     <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
		
	   <dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>${mybatis-plus.version}</version>
		</dependency>
		
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<scope>runtime</scope>
	</dependency>
	
	<!--druid -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>${druid.version}</version>
		</dependency>
		
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>fastjson</artifactId>
		<version>${fastjson.version}</version>
    </dependency>
    
    <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
   </dependency>
   
    <dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
  </dependency>

业务实现类

public interface UserService extends UserDetailsService
{
   Map<String,  Collection<ConfigAttribute>>loadResourceDefine();
}

@Service
@Transactional(rollbackFor=Exception.class)
public class UserServiceImpl implements UserService
{
    @Autowired
    private UserMapper userMapper;

    public UserDetails loadUserByUsername(String userId)
            throws UsernameNotFoundException
    {
        //通过userid查询用户信息
        User user= userMapper.getUserByUserId(userId);
        
        if(user==null)
        {
            throw new RuntimeException("用户名或密码不正确");
        }
        
        //获取角色信息
        List<Role> roles= userMapper.getRolesByUserOid(user.getOid());
        return new org.springframework.security.core.userdetails.User(user.getUserId(), user.getPwd(), roles);
    }
 }    

说明:业务实现类需要实现UserDetailsService接口,重写loadUserByUsername方法。

定义实体类

@TableName("fw_security_user")
public class User implements Serializable
{
    private static final long serialVersionUID = -2167962784070236658L;
    
    //设置主键名称
    @TableId(value="oid",type=IdType.AUTO)
    private Integer oid;
    
    @TableField(value="userId")
    private String userId;
    
    @TableField(value="pwd")
    private String pwd;
    
    @TableField(value="userName")
    private String userName;
    
    @TableField(value="active")
    private String active;
    
    @TableField(value="gender")
    private String gender;
    
    @TableField(value="email")
    private String email;
    
    @TableField(value="mobile")
    private String mobile;
   }
   
   @TableName(value = "fw_security_role")
public class Role implements GrantedAuthority ,Serializable
{
    private static final long serialVersionUID = 1455102065732503507L;

    @TableId(value="oid",type=IdType.AUTO)
    private Integer oid;
    
    @TableField(value="roleId")
    private String roleId;
    
    @TableField(value="active")
    private String active;
    
    @TableField(value="remark")
    private String remark;
    
    //返回权限信息
    public String getAuthority()
    {
        return "ROLE_"+roleId;
    }    
  }
    

Dao层实现

public interface UserMapper extends BaseMapper<User>
{
    User getUserByUserId(String userId);
    
 }
 
 xml定义:

 <select id="getUserByUserId" resultType="com.skywares.fw.security.pojo.User">
      select 
        oid,
        userId,
        userName,
        active,
        gender,
        mobile,
        email,
        pwd        
      from fw_security_user u 
      where u.userId=#{userId}
 </select>

认证配置

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    private UserServiceImpl userService;
    
    /**
     * 静态资源不访问
     */
    public void configure(WebSecurity web) throws Exception {
        //配置静态文件不需要认证
        web.ignoring().antMatchers("/js/**");
        web.ignoring().antMatchers("/html/**");
    }
    
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
    {
        //验证用户
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
    
    //密码加密处理
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * DSL编程方式
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login","/loing-error","/css/**","/js/**").permitAll()
                //任何请求需要认证
                .anyRequest().authenticated()
                // 登录页面
                .and().formLogin().loginPage("/login").loginProcessingUrl("/login")
                .failureUrl("/login-error").defaultSuccessUrl("/index")
                //登出
                .and().logout().logoutUrl("/logout")
                //未授权页面访问
                .exceptionHandling().accessDeniedPage( "/403" );
    }    
 }

说明:loginProcessingUrl:自定义登录请求地址 defaultSuccessUrl:登录成功跳转地址 failureUrl :登录失败跳转地址

Controller代码

@Controller
public class LoginController
{
    //登录请求
    @RequestMapping("/login")
    public String login() {
        return "login";
    }
    
    //登录成功
    @RequestMapping("/index")
    public String index() {
        return "index";
    }
    
    //登录错误,跳转到登录
    @RequestMapping("/login-error")
    public String loginError(Model model) {
        // 登录错误
        model.addAttribute("error", true);
        return "login";
    }
}

html页面

登录页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<p th:if="${error}" class="error">用户名或密码错误</p>
<form th:action="@{/login}" method="post">
    <label for="username">用户名</label>:
    <input type="text" id="username" name="username" autofocus="autofocus"/> <br/>
    <label for="password">用户密码</label>:
    <input type="password" id="password" name="password"/> <br/>
    <input type="submit" value="登录"/>
</form>
</body>
</html>

登录成功页面

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>admin page</title>
</head>
<body>
    success admin page!!!
</body>
</html>

测试

用户未登录请求,则跳转到登录页面

图片.png

用户登录,用户名或者密码错误

图片.png

说明:用户名或者密码错误会重定向到登录页面,且提示错误信息

用户登录,用户名和密码输出正确,则跳转到主页面

图片.png

总结

本文讲解了Spring Boot 集成Spring Security进行登录认证,采用的是用户名和密码的方式进行认证,下一章讲解权限和角色的授权,如有问题,请随时反馈。