SpringSecurity-SpirngBoot-显式声明用户名和使用UserDetailsService(二)

SpringSecurity-SpirngBoot-显式声明用户名和使用UserDetailsService(二)

在上一节中,我们实现了SpringSecurity和SpirngBoot的初步整合,没有进行任何配置就实现了一个登录认证的界面,并且在认证成功后可以访问到我们的后端资源。在这一章会介绍到使用InMemoryUserDetailsManagerUserDetailsService定制用户。

1.使用InMemoryUserDetailsManager显式声明用户名

在上一节的基础上,我们使用git创建一个新的分支spring-security-explicit

21944e3e90e7c116d1325f4d2df59c40.png

导入相关依赖

导入测试相关依赖,方便单元测试使用

<!--security测试模块-->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>
<!-- Selenium Web驱动 -->
<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>htmlunit-driver</artifactId>
</dependency>

编写SpringSecurity配置类

@Configurable
@EnableWebSecurity
public class SecurityConfiguration {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                //所有的请求都需要用户进行认证。
                .authorizeHttpRequests(
                        (authorize) -> authorize.anyRequest().authenticated()
                )
                //开启Basic认证
                .httpBasic(Customizer.withDefaults())
                //开启表单认证
                .formLogin(Customizer.withDefaults());
​
        return http.build();
    }
​
    @Bean
    public InMemoryUserDetailsManager userDetailService() {
        // 显示声明用户名密码
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("admin")
                .password("123456")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }
}

userDetailService方法中可以看到,我们注册了一个InMemoryUserDetailsManager对象到Spring bean容器中,并且向其构造函数传入了我们显式声明的user对象,用户名是admin,密码是123456。

启动项目,访问localhost:8080

第一次访问会跳转到登录页面:

aea3c86b6e55e2e51e828a2fb02733bd.png

输入我们定义的用户名和密码,点击登录:

a91347d0ff4a6570fe32fe1596071cbe.png

可以看到,我们成功访问到了之前定义的index.html页面

单元测试

编写以下单元测试代码,测试在未登录时访问,和使用MockUser登录访问的结果:

@SpringBootTest
@AutoConfigureMockMvc
class JackmouseSpringBootSecurityHelloApplicationTests {

	@Test
	void contextLoads() {
	}

	@Autowired
	private MockMvc mockMvc;

	@Test
	void indexWhenUnAuthenticatedThenRedirect() throws Exception {
		// @formatter:off
		this.mockMvc.perform(get("/"))
				.andExpect(status().isUnauthorized());
		// @formatter:on
	}

	@Test
	@WithMockUser
	void indexWhenAuthenticatedThenOk() throws Exception {
		// @formatter:off
		this.mockMvc.perform(get("/"))
				.andExpect(status().isOk());
		// @formatter:on
	}

}

1f7aeb6793f350df06a7e31613efd1f3.png

2.使用UserDetailsService定义用户

代码编写

创建CustomUser实体类
public class CustomUser {
    private final long id;

    private final String email;

    @JsonIgnore
    private final String password;

    @JsonCreator
    public CustomUser(long id, String email, String password) {
        this.id = id;
        this.email = email;
        this.password = password;
    }
    public long getId() {
        return this.id;
    }

    public String getEmail() {
        return this.email;
    }

    public String getPassword() {
        return this.password;
    }
}
创建CustomUserRepository接口
public interface CustomUserRepository {
    CustomUser findCustomUserByEmail(String email);
}
创建MapCustomUserRepository对CustomUserRepository接口实现
public class MapCustomUserRepository implements CustomUserRepository{

    private final Map<String, CustomUser> emailToCustomUser;

    public MapCustomUserRepository(Map<String, CustomUser> emailToCustomUser) {
        this.emailToCustomUser = emailToCustomUser;
    }

    @Override
    public CustomUser findCustomUserByEmail(String email) {
        return this.emailToCustomUser.get(email);
    }
}
SecurityConfiguration新增代码
  • 删除InMemoryUserDetailsManager的bean定义
  • 注入BCryptPasswordEncoder密码加密类
  • 初始化一个CustomUser供登录使用
@Bean
PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}


@Bean
MapCustomUserRepository userRepository() {
    String password = new BCryptPasswordEncoder().encode("123456");

    CustomUser customUser = new CustomUser(1L, "1310179240@qq.com", password);
    Map<String, CustomUser> emailToCustomUser = new HashMap<>();
    emailToCustomUser.put(customUser.getEmail(), customUser);
    return new MapCustomUserRepository(emailToCustomUser);
}
创建CustomUserRepositoryUserDetailsService实现UserDetailsService
@Service
public class CustomUserRepositoryUserDetailsService implements UserDetailsService {
    private final CustomUserRepository userRepository;

    public CustomUserRepositoryUserDetailsService(CustomUserRepository userRepository) {
        this.userRepository = userRepository;
    }
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 查询用户名对应的用户
        CustomUser customUser = this.userRepository.findCustomUserByEmail(username);
        if (customUser == null) {
            // 用户不存在 抛出异常
            throw new UsernameNotFoundException("username " + username + " is not found");
        }
        return new CustomUserDetails(customUser);
    }

    static final class CustomUserDetails extends CustomUser implements UserDetails {

        private static final List<GrantedAuthority> ROLE_USER = Collections
                .unmodifiableList(AuthorityUtils.createAuthorityList("ROLE_USER"));

        CustomUserDetails(CustomUser customUser) {
            super(customUser.getId(), customUser.getEmail(), customUser.getPassword());
        }

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

        @Override
        public String getUsername() {
            return getEmail();
        }

        @Override
        public boolean isAccountNonExpired() {
            return true;
        }

        @Override
        public boolean isAccountNonLocked() {
            return true;
        }

        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }

        @Override
        public boolean isEnabled() {
            return true;
        }

    }
}

代码实现了SpringSecurity定义的UserDetailsService接口,loadUserByUsername是SpringSecurity在进行登录验证时获取用户信息的模板方法,在我们对loadUserByUsername进行重写后,SpringSecurity会使用我们定义的loadUserByUsername方法获取用户信息。

userRepository中保存了我们在SecurityConfiguration注入的用户名为1310179240@qq.com,密码为BCryptPasswordEncoder加密后的123456.

定义CurrentUser注解
@AuthenticationPrincipal
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}

@AuthenticationPrincipal的作用时自动注入当前登录用户信息。

创建UserController类
@RestController
public class UserController {
    @GetMapping("/user")
    public CustomUser user(@CurrentUser CustomUser currentUser) {
        return currentUser;
    }
}

这里的@CurrentUser就会把当前登录的用户信息注入到currentUser中。

启动项目,访问localhost:8080/user

在登陆页面输入用户名和密码,点击登录:

09d39eb4f22158ecc7e192f218e59f1c.png

可以看到我们成功访问到了用户的相关信息。

单元测试

UserDetailsServiceTests.java:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserDetailsServiceTests {
    @Autowired
    private TestRestTemplate rest;

    @Test
    void login() {
        CustomUser result = this.rest.withBasicAuth("1310179240@qq.com", "123456")
                .getForObject("/user", CustomUser.class);
        assertThat(result.getEmail()).isEqualTo("1310179240@qq.com");
    }
}

4e74b37ebf50e16099404a3b1cabf499.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值