SpringBoot集成Spring Security(1)——登录认证

一、前言

权限认证框架最常见的除了Shiro,另一个就是Spring Security,相比Shiro而言Spring Security学习难度较大,这里我通过博客记录下自己学习Spring Security的历程。
如果您跟随博客学习,请尽量保持环境一致,否则掉坑了不负责哈O(∩_∩)O~

环境:SpringBoot2.3.6.RELEASE,Spring Security5+

二、环境依赖

这里使用的SpringBoot是2.3.6,Spring Security默认是5.3.5

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>

三、数据库

这里开始简单点,只涉及到三张表,user、role、user_role,先不处理权限问题。

  • user表
CREATE TABLE `sys_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', '123');
INSERT INTO `sys_user` VALUES (2, 'customer', '111');
  • role表
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'ROLE_admin');
INSERT INTO `role` VALUES (2, 'ROLE_customer');

PS:角色名是‘ROLE_xxx’格式,下面做说明

  • user_role表
CREATE TABLE `user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL,
  `role_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 2, 2);

四、代码

1、配置文件

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3307/security?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456

#开启Mybatis下划线命名转驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true

logging.level.com.lr.security.dao=debug
2、model

数据表对应的java实体类我这里就不贴出来了,没啥特别的地方。

3、dao
  • SysUserMapper
@Mapper
public interface SysUserMapper {

    @Select("select * from user where id = #{id}")
    SysUser selectById(Integer id);

    @Select("select * from user where username = #{username}")
    SysUser selectByUserName(String username);

}
  • RoleMapper
@Mapper
public interface RoleMapper {

    @Select("select * from role where id = #{id}")
    Role selectById(Integer id);

    @Select("select * from role where role_name = #{roleName}")
    Role selectByRoleName(String roleName);

}
  • UserRoleMapper
@Mapper
public interface UserRoleMapper {

    @Select("select * from user_role where user_id = #{userId}")
    List<UserRole> selectByUserId(Integer userId);

}
4、service
  • SysUserService
@Service
public class SysUserService {

    @Autowired
    private SysUserMapper userMapper;

    public SysUser selectById(Integer id) {
        return userMapper.selectById(id);
    }

    public SysUser selectByName(String username) {
        return userMapper.selectByUserName(username);
    }
}
  • RoleService
@Service
public class RoleService {

    @Autowired
    private RoleMapper roleMapper;

    public Role selectById(Integer id){
        return roleMapper.selectById(id);
    }

}
  • UserRoleService
@Service
public class UserRoleService {

    @Autowired
    private UserRoleMapper userRoleMapper;

    public List<UserRole> listByUserId(Integer userId) {
        return userRoleMapper.selectByUserId(userId);
    }
}
5、controller
@Controller
public class LoginController {

    @RequestMapping("/loginPage")
    public String login(){
        return "login.html";
    }

    @RequestMapping("/main")
    public String toMain(Authentication authentication){ // 会自动注入Authentication
        System.out.println("登录用户:" + authentication.getName());
        return "main.html";
    }

    @PreAuthorize("hasRole('ROLE_admin')")
    @RequestMapping("/admin")
    @ResponseBody
    public String admin(){
        return "你是管理员角色";
    }

    @PreAuthorize("hasRole('ROLE_customer')")
    @RequestMapping("/customer")
    @ResponseBody
    public String customer(){
        return "你是普通用户角色";
    }
}

1.在toMain方法中有个参数authentication,这个对象包含认证后用户的一些信息,并且可注入到springmvc
2.@PreAuthorize注解表示在执行该注解标识的方法前,需要认证。这里调用hasRole方法进行角色判断,参数即为该方法为哪些角色可执行,这里也解释下上面建表时角色名称格式问题。默认情况下未做其他配置时,hasRole方法的源码如下:
在这里插入图片描述
从源码可看出在判断角色名称时,是带有一个默认前缀“ROLE_”,同时从4可以看出其实在注解中我们可以省略掉前缀,因为最终也会给加上

6、核心:Security的配置类
6.1 LoginUserDetailsService

在给Spring Security配置前还需定义用于认证用户详情的类LoginUserDetailsService,实现UserDetailsService接口

@Service
public class LoginUserDetailsService implements UserDetailsService {

    @Autowired
    private SysUserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private UserRoleService userRoleService;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// 通过用户名查询数据库
        SysUser sysUser = userService.selectByName(username);
        if (sysUser == null) throw new UsernameNotFoundException("用户名不存在");

        List<GrantedAuthority> authorities = new ArrayList<>();
        List<UserRole> userRoles = userRoleService.listByUserId(sysUser.getId());
        for (UserRole userRole : userRoles) {
            Role role = roleService.selectById(userRole.getRoleId());
            //SimpleGrantedAuthority实现了GrantedAuthority
            authorities.add(new SimpleGrantedAuthority(role.getRoleName())); // 角色的信息
        }
        // UserDetails有个实现类User
        // LoginUser可参照User,自己定义一个UserDetails,可便于后期拓展,若不想麻烦那就直接用Security提供的User
        // return new User(sysUser.getUsername(),sysUser.getPassword(),authorities);
        return new LoginUser(sysUser,authorities);
    }
}
/**
 * 登录用户身份权限
 */
public class LoginUser implements UserDetails {
    private static final long serialVersionUID = 1L;

    private SysUser sysUser;
    private List<GrantedAuthority> authorities;

    public LoginUser(SysUser sysUser, List<GrantedAuthority> authorities) {
        this.sysUser = sysUser;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return sysUser.getPassword();
    }

    @Override
    public String getUsername() {
        return sysUser.getUsername();
    }

    /**
     * 账户是否未过期,过期无法验证
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否可用 ,禁用的用户不能身份验证
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}
6.2 重点:SecurityConfig

创建Spring Security的配置类SecurityConfig,继承WebSecurityConfigurerAdapter。

1、配置类上添加注解:@EnableGlobalMethodSecurity(prePostEnabled = true),表示开启spring方法级安全,prePostEnabled = true表示启用@PreAuthorize 和@PostAuthorize 两个注解,controller中有用到的
2、引入上面创建的LoginUserDetailsService,同时重写configure方法。

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService loginUserDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        		// CSRF(跨站请求伪造)禁用,默认开启,会检测请求中是否包含令牌,没有则拒绝并返回403,我们现在不考虑这个,所以要禁用掉
                .csrf().disable() 
                .authorizeRequests()
                // 对静态资源放行(示例)
                //.mvcMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js") .permitAll()
                // 指定可匿名访问的路径(示例)
                //.mvcMatchers("xxx","zzz/yyy").anonymous()
                .anyRequest().authenticated()// 除了上面其他都必须鉴权
                .and()
                .formLogin().loginPage("/loginPage")// 未认证时访问跳转登录页面
                .loginProcessingUrl("/doLogin")// 表单登录url设置,默认/login
                // 登录成功跳转
                .defaultSuccessUrl("/main",true)
                .permitAll()
                .and()
                .logout().permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(loginUserDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(charSequence.toString());
            }
        };
    }
}

passwordEncoder(passwordEncoder())中指定了密码的校验,在passwordEncoder()中的matches指明了当前密码是直接比较。因为我们数据库是存的明文,但是Security默认密码编码器都是有加密的,这个具体详情可在AuthenticationConfiguration类中查看,这里不做陈述。所以我们当前明文的话就需要自己制定下密码校验规则。

7、页面

页面直接放在static目录下,如果引入的thymeleaf依赖,这页面需要放在templates目录下

注意几点:
1、form的提交地址要与SecurityConfig 指定的一致,且必须是POST请求
2、用户名、密码的名称必须为username、password,这个是security中默认的,可在UsernamePasswordAuthenticationFilter看到。当然这个也是可以在配置类中自定定义名称

  • login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/doLogin" method="post">
    <input name="username"/>
    <input name="password" type="password"/>
    <button type="submit">登录</button>
</form>
</body>
</html>
  • main.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录成功,
<a href="/admin">admin</a>
<a href="/customer">customer</a>
</body>
</html>

在这里插入图片描述

五、运行

在这里插入图片描述

本文借鉴了https://jitwxs.blog.csdn.net/article/details/80249235

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
好的,下面是 SpringBoot 集成 SpringSecurity 实现登录和权限管理的示例代码: 首先,我们需要在 pom.xml 中添加 SpringSecurity 的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 然后,我们需要创建一个继承了 WebSecurityConfigurerAdapter 的配置类,并且使用 @EnableWebSecurity 注解启用 SpringSecurity: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasRole("USER") .anyRequest().authenticated() .and() .formLogin().loginPage("/login").permitAll() .and() .logout().permitAll(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` 在上面的代码中,我们通过 configure(AuthenticationManagerBuilder auth) 方法指定了使用哪个 UserDetailsService 来获取用户信息,通过 configure(HttpSecurity http) 方法配置了哪些 URL 需要哪些角色才能访问,以及登录页面和退出登录的 URL。 接下来,我们需要实现 UserDetailsService 接口,用来获取用户信息: ```java @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("用户不存在"); } List<GrantedAuthority> authorities = new ArrayList<>(); for (Role role : user.getRoles()) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities); } } ``` 在上面的代码中,我们通过 UserRepository 来获取用户信息,并且将用户的角色转换成 GrantedAuthority 对象。 最后,我们需要创建一个控制器来处理登录和退出登录的请求: ```java @Controller public class LoginController { @GetMapping("/login") public String login() { return "login"; } @GetMapping("/logout") public String logout(HttpServletRequest request, HttpServletResponse response) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { new SecurityContextLogoutHandler().logout(request, response, authentication); } return "redirect:/login?logout"; } } ``` 在上面的代码中,我们通过 @GetMapping 注解来处理登录和退出登录的请求,并且在退出登录成功后重定向到登录页面。 以上就是 SpringBoot 集成 SpringSecurity 实现登录和权限管理的示例代码,希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值