• Spring Security基本框架之用户定义


    本文内容来自王松老师的《深入浅出Spring Security》,自己在学习的时候为了加深理解顺手抄录的,有时候还会写一些自己的想法。

            在前面的案例中,我们登陆的用户信息是基于配置文件来配置的,其本质上是基于内存来实现的。但是在实际的开发中这种方式是不可取的,实际开发中都是要将用户信息存储在数据库中的。

            Spring Security支持多种用户定义的方式,接下来我们逐个看下这些定意思方式。通过前面的学习我们配置好UserDetailsService,将UserDetailsService配置给AuthenticationManagerBuilder,系统在将UserDetailsService提供给AuthentncationProvider使用即可。

    基于内存(InMemoryUserDetailsManager)

            前面的案例中我们是在配置文件中配置用户的信息,实际上也是也是基于内存。只是没有将InMemoryUserDetailsManager类明确抽出来自定义,现在我们通过自定义InMemoryUserDetailsManager来看一下基于内存如何自定义。

            重写WebSecurityConfigurerAdapter类的configure(AuthenticationManagerBuilder auth)方法,内容如下:

    1. @Override
    2. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    3. InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
    4. inMemoryUserDetailsManager.createUser(
    5. User
    6. .withUsername("tlh")
    7. .password("{noop}123456")
    8. .authorities("admin", "users")
    9. .build());
    10. auth.userDetailsService(inMemoryUserDetailsManager);
    11. }

            首先创建一个InMemoryUserDetailsManager的实例,调用该实例的createUser方法来创建用户,我们设置了用户的账户名、密码、设置了用户的权限。注意的是这里采用明文的方式存储密码,就是在密码的前面添加了{noop}。如果没有配置的话,系统会提示没有匹配到PasswordEncoder。

    基于数据库(JdbcUserDetailsManager

            JdbcUserDetailsManager也是Spring Security提供的一个基于数据库保存用户信息的一种策略。但是我们一般也不会使用,因为使用JdbcUserDetailsManager的话有一定的局限性,其已经写好了sql,所以一般在实际开发中也不会使用。见JdbcUserDetailsManager类中的定义:

    1. public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsManager, GroupManager {
    2. public static final String DEF_CREATE_USER_SQL = "insert into users (username, password, enabled) values (?,?,?)";
    3. public static final String DEF_DELETE_USER_SQL = "delete from users where username = ?";
    4. public static final String DEF_UPDATE_USER_SQL = "update users set password = ?, enabled = ? where username = ?";
    5. public static final String DEF_INSERT_AUTHORITY_SQL = "insert into authorities (username, authority) values (?,?)";
    6. public static final String DEF_DELETE_USER_AUTHORITIES_SQL = "delete from authorities where username = ?";
    7. public static final String DEF_USER_EXISTS_SQL = "select username from users where username = ?";
    8. public static final String DEF_CHANGE_PASSWORD_SQL = "update users set password = ? where username = ?";
    9. public static final String DEF_FIND_GROUPS_SQL = "select group_name from groups";
    10. public static final String DEF_FIND_USERS_IN_GROUP_SQL = "select username from group_members gm, groups g where gm.group_id = g.id and g.group_name = ?";
    11. public static final String DEF_INSERT_GROUP_SQL = "insert into groups (group_name) values (?)";
    12. public static final String DEF_FIND_GROUP_ID_SQL = "select id from groups where group_name = ?";
    13. public static final String DEF_INSERT_GROUP_AUTHORITY_SQL = "insert into group_authorities (group_id, authority) values (?,?)";
    14. public static final String DEF_DELETE_GROUP_SQL = "delete from groups where id = ?";
    15. public static final String DEF_DELETE_GROUP_AUTHORITIES_SQL = "delete from group_authorities where group_id = ?";
    16. public static final String DEF_DELETE_GROUP_MEMBERS_SQL = "delete from group_members where group_id = ?";
    17. public static final String DEF_RENAME_GROUP_SQL = "update groups set group_name = ? where group_name = ?";
    18. public static final String DEF_INSERT_GROUP_MEMBER_SQL = "insert into group_members (group_id, username) values (?,?)";
    19. public static final String DEF_DELETE_GROUP_MEMBER_SQL = "delete from group_members where group_id = ? and username = ?";
    20. public static final String DEF_GROUP_AUTHORITIES_QUERY_SQL = "select g.id, g.group_name, ga.authority from groups g, group_authorities ga where g.group_name = ? and g.id = ga.group_id ";
    21. public static final String DEF_DELETE_GROUP_AUTHORITY_SQL = "delete from group_authorities where group_id = ? and authority = ?";
    22. protected final Log logger = LogFactory.getLog(this.getClass());
    23. private String createUserSql = "insert into users (username, password, enabled) values (?,?,?)";
    24. private String deleteUserSql = "delete from users where username = ?";
    25. private String updateUserSql = "update users set password = ?, enabled = ? where username = ?";
    26. private String createAuthoritySql = "insert into authorities (username, authority) values (?,?)";
    27. private String deleteUserAuthoritiesSql = "delete from authorities where username = ?";
    28. private String userExistsSql = "select username from users where username = ?";
    29. private String changePasswordSql = "update users set password = ? where username = ?";
    30. private String findAllGroupsSql = "select group_name from groups";
    31. private String findUsersInGroupSql = "select username from group_members gm, groups g where gm.group_id = g.id and g.group_name = ?";
    32. private String insertGroupSql = "insert into groups (group_name) values (?)";
    33. private String findGroupIdSql = "select id from groups where group_name = ?";
    34. private String insertGroupAuthoritySql = "insert into group_authorities (group_id, authority) values (?,?)";
    35. private String deleteGroupSql = "delete from groups where id = ?";
    36. private String deleteGroupAuthoritiesSql = "delete from group_authorities where group_id = ?";
    37. private String deleteGroupMembersSql = "delete from group_members where group_id = ?";
    38. private String renameGroupSql = "update groups set group_name = ? where group_name = ?";
    39. private String insertGroupMemberSql = "insert into group_members (group_id, username) values (?,?)";
    40. private String deleteGroupMemberSql = "delete from group_members where group_id = ? and username = ?";
    41. private String groupAuthoritiesSql = "select g.id, g.group_name, ga.authority from groups g, group_authorities ga where g.group_name = ? and g.id = ga.group_id ";
    42. private String deleteGroupAuthoritySql = "delete from group_authorities where group_id = ? and authority = ?";
    43. //省略。。。。
    44. }

    基于数据库(自定义实现UserDetailsService,基于MyBatis

            使用MyBatis做持久化目前是大多数企业应用采用的方案,Spring Security结合MyBatis可以灵活的制定用户表以及角色表。

            首先需要设计三张表,分别是用户表、角色表、以及用户和角色关联表。三张表关系入下图:

             用户和角色的关系是多对多的关系,我们使用user_role将两者关联起来。

            数据库表脚本如下:

    1. //创建表
    2. CREATE TABLE `role` (
    3. `id` int(11) NOT NULL AUTO_INCREMENT,
    4. `name` varchar(32) DEFAULT NULL,
    5. `nameZh` varchar(32) DEFAULT NULL,
    6. PRIMARY KEY (`id`)
    7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    8. CREATE TABLE `user` (
    9. `id` int(11) NOT NULL AUTO_INCREMENT,
    10. `username` varchar(32) DEFAULT NULL,
    11. `password` varchar(255) DEFAULT NULL,
    12. `enabled` tinyint(1) DEFAULT NULL,
    13. `accountNonExpired` tinyint(1) DEFAULT NULL,
    14. `accountNonLocked` tinyint(1) DEFAULT NULL,
    15. `credentialsNonExpired` tinyint(1) DEFAULT NULL,
    16. PRIMARY KEY (`id`)
    17. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    18. CREATE TABLE `user_role` (
    19. `id` int(11) NOT NULL AUTO_INCREMENT,
    20. `uid` int(11) DEFAULT NULL,
    21. `rid` int(11) DEFAULT NULL,
    22. PRIMARY KEY (`id`),
    23. KEY `uid` (`uid`),
    24. KEY `rid` (`rid`)
    25. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    26. //插入测试数据
    27. INSERT INTO `role` (`id`, `name`, `nameZh`)
    28. VALUES
    29. (1,'ROLE_dba','数据库管理员'),
    30. (2,'ROLE_admin','系统管理员'),
    31. (3,'ROLE_user','用户');
    32. INSERT INTO `user` (`id`, `username`, `password`, `enabled`,
    33. `accountNonExpired`, `accountNonLocked`, `credentialsNonExpired`)
    34. VALUES
    35. (1,'root','{noop}123',1,1,1,1),
    36. (2,'admin','{noop}123',1,1,1,1),
    37. (3,'sang','{noop}123',1,1,1,1);
    38. INSERT INTO `user_role` (`id`, `uid`, `rid`)
    39. VALUES
    40. (1,1,1),
    41. (2,1,2),
    42. (3,2,2),
    43. (4,3,3);

            在项目的bom依赖中引入MyBatis Plus和mySql的依赖:

    1. org.mybatis.spring.boot
    2. mybatis-spring-boot-starter
    3. 2.1.3
    4. mysql
    5. mysql-connector-java
    6. 8.0.16

            在配置文件中配置数据库基本连接信息:

    1. spring.datasource.username=root
    2. spring.datasource.password=123456
    3. spring.datasource.url=jdbc:mysql://localhost:3306/spring_security_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

            接下来创建用户和角色类:

    1. /**
    2. * @author tlh
    3. * @date 2022/11/17 23:47
    4. */
    5. public class User implements UserDetails {
    6. private Integer id;
    7. private String username;
    8. private String password;
    9. private Boolean enabled;
    10. private Boolean accountNonExpired;
    11. private Boolean accountNonLocked;
    12. private Boolean credentialsNonExpired;
    13. private List roles = new ArrayList<>();
    14. @Override
    15. public Collectionextends GrantedAuthority> getAuthorities() {
    16. List authorities = new ArrayList<>();
    17. for (Role role : roles) {
    18. authorities.add(new SimpleGrantedAuthority(role.getName()));
    19. }
    20. return authorities;
    21. }
    22. @Override
    23. public String getPassword() {
    24. return password;
    25. }
    26. @Override
    27. public String getUsername() {
    28. return username;
    29. }
    30. @Override
    31. public boolean isAccountNonExpired() {
    32. return accountNonExpired;
    33. }
    34. @Override
    35. public boolean isAccountNonLocked() {
    36. return accountNonLocked;
    37. }
    38. @Override
    39. public boolean isCredentialsNonExpired() {
    40. return credentialsNonExpired;
    41. }
    42. @Override
    43. public boolean isEnabled() {
    44. return enabled;
    45. }
    46. //省略get/set方法
    47. }
    48. /**
    49. * @author tlh
    50. * @date 2022/11/17 23:47
    51. */
    52. public class Role {
    53. private Integer id;
    54. private String name;
    55. private String nameZh;
    56. //省略get/set方法
    57. }

            接下来自定义UserDetailsService:

    1. /**
    2. * @author tlh
    3. * @date 2022/11/17 23:52
    4. */
    5. @Component
    6. public class MyUserDetailsService implements UserDetailsService {
    7. @Autowired
    8. private UserMapper userMapper;
    9. @Override
    10. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    11. User user = userMapper.loadUserByUsername(username);
    12. if (user == null) {
    13. throw new UsernameNotFoundException("用户不存在");
    14. }
    15. user.setRoles(userMapper.getRolesByUid(user.getId()));
    16. return user;
    17. }
    18. }

            为了方便测试我们将UserMapper.xml和UserMapper接口放在相同的包下。

    1. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    2. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    3. "com.tlh.secority.secoritydemo.mapper.UserMapper">

            为了防止maven打包是忽略掉xml文件,我们还需要在pom.xml中添加如下配置:

    1. src/main/java
    2. **/*.xml
    3. src/main/resources

            最后一步,在Spring Security的配置类中将我们自定义的UserDetailsService添加进去:

    1. /**
    2. * @author tlh
    3. * @date 2022/11/16 21:11
    4. */
    5. @Configuration
    6. public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    7. @Autowired
    8. private UserDetailsService userDetailsService;
    9. @Override
    10. protected void configure(HttpSecurity http) throws Exception {
    11. http.authorizeRequests()
    12. .anyRequest().authenticated()
    13. .and()
    14. .formLogin()
    15. .loginPage("/login.html")
    16. .loginProcessingUrl("/doLogin")
    17. .successHandler(getAuthenticationSuccessHandler())
    18. .failureUrl("/login.html")
    19. .usernameParameter("uname")
    20. .passwordParameter("passwd")
    21. .permitAll()
    22. .and()
    23. .csrf().disable();
    24. }
    25. @Override
    26. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    27. //内存方式
    28. // InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
    29. // inMemoryUserDetailsManager.createUser(
    30. // User
    31. // .withUsername("tlh")
    32. // .password("{noop}123456")
    33. // .authorities("admin", "users")
    34. // .build());
    35. // auth.userDetailsService(inMemoryUserDetailsManager);
    36. //数据库方式
    37. auth.userDetailsService(userDetailsService);
    38. }
    39. AuthenticationSuccessHandler getAuthenticationSuccessHandler() {
    40. return new AuthenticationSuccessHandler() {
    41. @Override
    42. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    43. response.setContentType("application/json;charset=utf-8");
    44. Map respMap = new HashMap<>(2);
    45. respMap.put("code", "200");
    46. respMap.put("msg", "登录成功");
    47. ObjectMapper objectMapper = new ObjectMapper();
    48. String jsonStr = objectMapper.writeValueAsString(respMap);
    49. response.getWriter().write(jsonStr);
    50. }
    51. };
    52. }
    53. }

            至此,整个配置工作就算完成了。接下来启动项目,利用数据库中添加的用户进行登录测试,就可以登录成功了。

  • 相关阅读:
    R语言ggplot2可视化分面折线图:使用facet_wrap函数可视化分面折线图、color参数和size参数自定义线条的颜色和宽度粗细
    阿里首次开源 Java 10万字八股文,Github仅一天星标就超60K
    手把手教你使用 Spring Boot 3 开发上线一个前后端分离的生产级系统(七) - Elasticsearch 8.2 集成与配置
    typscript中逆变与协变
    ELK介绍
    Leetcode 105. 从前序与中序遍历序列构造二叉树
    java计算机毕业设计小型健身俱乐部网站源码+系统+数据库+lw文档+mybatis+运行部署
    (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
    Liunx 日志 常规命令查询
    学习操作系统之分时系统+时间片
  • 原文地址:https://blog.csdn.net/qq_27062249/article/details/127913639