SpringSecurity - Quick Start Demo And Know What Did It Do ?

该博客主要围绕Java开发展开,介绍了SpringMVC和安全配置的实现步骤。包括实现WebMvcConfigurer接口配置SpringMVC,实现GrantedAuthority接口处理权限,定义实体附带权限集合,实现AuthenticationSuccessHandler处理验证成功后续操作等,还提及了启动类及前端页面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一. 实现WebMvcConfigurer接口 

二. 实现GrantedAuthority接口

三. 定义实体(附带权限集合) 

四. 实现AuthenticationSuccessHandler

五. 实现UserDetailsService接口

六. 继承WebSecurityConfigurerAdapter

七. 启动类及前端页面


 

java代码以及html页面 copy from https://blog.youkuaiyun.com/vbirdbest/article/details/90145383 

其他皆为原创。

 pom.xml如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

首先来配置SpringMVC相关的内容。通过实现WebMvcConfigurer的addViewControllers()方法 

1. 对于WebMvcConfigurer,看下它的源码注释。如下:

/**
 * Defines callback methods to customize the Java-based configuration for
 * Spring MVC enabled via {@code @EnableWebMvc}.
 *
 * <p>{@code @EnableWebMvc}-annotated configuration classes may implement
 * this interface to be called back and given a chance to customize the
 * default configuration.
 *
 * @author Rossen Stoyanchev
 * @author Keith Donald
 * @author David Syer
 * @since 3.1
 */

是经由@EnableWebMvc,使我们设置的SpringMVC配置生效。(所以,要有一个类标注@EnableWebMvc注解,这里可以选择启动类)。通过实现WebMvcConfigurer的相关方法来自定义我们的SpringMVC配置。

2. 再来看它的addViewController方法。源码注释,如下:

       /**
	 * Configure simple automated controllers pre-configured with the response
	 * status code and/or a view to render the response body. This is useful in
	 * cases where there is no need for custom controller logic -- e.g. render a
	 * home page, perform simple site URL redirects, return a 404 status with
	 * HTML content, a 204 with no content, and more.
	 */

 其实就是表达了它的作用是建立请求的url与视图之间的映射关系。通过url去找要跳转的视图。将这种自动化的行为配置给controller。

3. 同样地,去找ViewControllerRegistry的源码。

实际上是通过它来把我上面提到的那种映射关系变成了现实。

/**
 * Assists with the registration of simple automated controllers pre-configured
 * with status code and/or a view.
 *
 * @author Rossen Stoyanchev
 * @author Keith Donald
 * @since 3.1
 */

 ViewControllerRegistry的解释是:帮助注册controller前置配置(映射关系)的自动配置。

4. 查看addViewController方法的源码:

        /**
	 * Map a view controller to the given URL path (or pattern) in order to render
	 * a response with a pre-configured status code and view.
	 * <p>Patterns like {@code "/admin/**"} or {@code "/articles/{articlename:\\w+}"}
	 * are allowed. See {@link org.springframework.util.AntPathMatcher} for more details on the
	 * syntax.
	 */
	public ViewControllerRegistration addViewController(String urlPath) {
		ViewControllerRegistration registration = new ViewControllerRegistration(urlPath);
		registration.setApplicationContext(this.applicationContext);
		this.registrations.add(registration);
		return registration;
	}

将url信息放到了ViewControllerRegistration实体中,然后将这个实体放到了ViewControllerRegistration实体集合中。 应用程序上下文暂时不讲解。最后它返回了这个实体。

5.查看setViewName源码

      /**
	 * Set the view name to return. Optional.
	 * <p>If not specified, the view controller will return {@code null} as the
	 * view name in which case the configured {@link RequestToViewNameTranslator}
	 * will select the view name. The {@code DefaultRequestToViewNameTranslator}
	 * for example translates "/foo/bar" to "foo/bar".
	 * @see org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
	 */
	public void setViewName(String viewName) {
		this.controller.setViewName(viewName);
	}

这样ViewControllerRegistration同时拥有了url和view。 再底层,我记得是DefaultRequestToViewNameTranslator具体完成uri 到 view或者视图名的解析 。

一. 实现WebMvcConfigurer接口 

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/index").setViewName("index");
        registry.addViewController("/getUserList").setViewName("userList");
        registry.addViewController("/getOrderList").setViewName("orderList");
    }
}

二. 实现GrantedAuthority接口

 

@Data
@AllArgsConstructor
public class SysPermission implements GrantedAuthority {

    private Long id;

    private String name;

    private String code;

    private String url;

    private String method;

    @Override
    public String getAuthority() {
        return "ROLE_" + this.code + ":" + this.method.toUpperCase();
    }
}

先来看下注解@Data和@AllArgsConstructor

@Data是lombok的注解。@Data是@RequiredArgsConstructor、@ToString、@Getter、@Setter、@EqualsAndHashCode五个注解的集合体。

@RequiredArgsConstructor是对final修饰的、@NonNull标注的字段生成构造器。

@ToString 重写了toString方法

@Getter、@Setter 就是生成平常的get set方法

@EqualsAndHashCode 重写equals和hashcode方法。

@AllArgsConstructor为所有成员属性生成构造器。

接下来是关键代码讲解。

public interface GrantedAuthority extends Serializable {
	
	String getAuthority();
}

GrantedAuthority表示将已经授予的权限给某个实体。也就是权限的抽象概念的实体类

getAuthority方法表示返回GrantedAuthority的字符串表现形式。必须能清晰准确表示权限。依赖Access Control Manager。

且以ROLE_ 为前缀。

上面的实现类返回了ROLE_ + 权限的名称 + 访问的方法 ,继而构成了权限字符串。表示拥有对某方法的访问的某权限。

三. 定义实体(附带权限集合) 

@Data
@AllArgsConstructor
public class SysUser {

    private Long id;

    private String username;

    private String password;

    private List<SysPermission> sysPermissions;
}

 username、password、sysPermissions是必要的字段。可以换成其他名字。

四. 实现AuthenticationSuccessHandler

@Slf4j
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final ObjectMapper objectMapper;

    @Autowired
    public MyAuthenticationSuccessHandler(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        log.info("login successfully {}", objectMapper.writeValueAsString(authentication));
        httpServletResponse.sendRedirect("/index");
    }
}

 验证通过后的后续处理。

onAuthenticationSuccess方法:当用户成功验证的时候,会被调用。明显是基于事件驱动的方式。

这里,log输出了Authentication实体。并且重定向到 /index,也就是index.html。

在这个方法中的第三个参数Authentication实体。

从源码中看一下。

public interface Authentication extends Principal, Serializable {
	
	Collection<? extends GrantedAuthority> getAuthorities();


	Object getCredentials();


	Object getDetails();

	
	Object getPrincipal();

	
	boolean isAuthenticated();

	
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

Authentication是在验证过程中创建的一个接口实体。

getAuthorities:获取权限集合

getCredentials:用来证明principal是否正确。通常是个密码,也可以是跟AuthenticationManager相关联的内容。

getDetails:返回验证请求的一些额外信息。比如remoteAddress、sessionid等。

getPrincipal:返回定义的一些规范,比如username 、password 、authorities 、accountNonExpired 、 accountNonLocked、 credentialsNonExpired 、enabled

 

debug中可以看出是传入的是UsernamePasswordAuthenticationToken这个实现类。

五. 实现UserDetailsService接口

@Component
public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = new SysUser(1L, "admin", "$2a$10$VRmgVqXDdnA0RtaEum2fw.cAIn9qCIMQQtNm.DzX5AAvUVtKvpNdG",
                Arrays.asList(
                        new SysPermission(1L, "用户列表", "user:view", "/getUserList", "GET"),
                        new SysPermission(2L, "添加用户", "user:add", "/addUser", "POST"),
                        new SysPermission(3L, "修改用户", "user:update", "/updateUser", "PUT")
                ));
        if (sysUser == null) {
            throw new UsernameNotFoundException(username);
        }
        return new User(sysUser.getUsername(), sysUser.getPassword(), sysUser.getSysPermissions());
    }

    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
    }
}

sysUser的创建 是模拟从数据库中 通过找到username 的实体。这里,偷懒了。

对于密码的加密采用了BCryptPasswordEncoder的encode加密方式。

接下来讲解UserDetailsService接口。

/**
 * Core interface which loads user-specific data.
 * <p>
 * It is used throughout the framework as a user DAO and is the strategy used by the
 * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider
 * DaoAuthenticationProvider}.
 *
 * <p>
 * The interface requires only one read-only method, which simplifies support for new
 * data-access strategies.
 *
 * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider
 * @see UserDetails
 *
 * @author Ben Alex
 */
public interface UserDetailsService {
	// ~ Methods
	// ========================================================================================================

	/**
	 * Locates the user based on the username. In the actual implementation, the search
	 * may possibly be case sensitive, or case insensitive depending on how the
	 * implementation instance is configured. In this case, the <code>UserDetails</code>
	 * object that comes back may have a username that is of a different case than what
	 * was actually requested..
	 *
	 * @param username the username identifying the user whose data is required.
	 *
	 * @return a fully populated user record (never <code>null</code>)
	 *
	 * @throws UsernameNotFoundException if the user could not be found or the user has no
	 * GrantedAuthority
	 */
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

加载用户指定数据的核心接口。

通常被当做dao,是由DaoAuthenticationProvider提供的策略。

这个接口只要求一个只读方法。

再来看UserDetails的源码:

public interface UserDetails extends Serializable {
	
	Collection<? extends GrantedAuthority> getAuthorities();

	
	String getPassword();

	
	String getUsername();

	
	boolean isAccountNonExpired();

	
	boolean isAccountNonLocked();


	boolean isCredentialsNonExpired();


	boolean isEnabled();
}

不再详细叙述。

接着看它的实现类User

    public User(String username, String password,
			Collection<? extends GrantedAuthority> authorities) {
		this(username, password, true, true, true, true, authorities);
	}

我们这里只使用了它的一个构造器。

六. 继承WebSecurityConfigurerAdapter

 

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Autowired
    private MyAuthenticationSuccessHandler successHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 禁止跨站请求伪造
        http.csrf()
                .disable()
            .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .successHandler(successHandler)
                .failureUrl("/login?error")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

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

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

七. 启动类及前端页面

@SpringBootApplication
public class Springboot2Application {

    public static void main(String[] args) {
        SpringApplication.run(Springboot2Application.class, args);
    }

}

 orderList.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
</head>
<body>
订单列表
</body>
</html>

userList.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
</head>
<body>
用户列表
</body>
</html>

index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="utf-8">
</head>
<body>
<div sec:authorize="isAuthenticated()">
    <p>登录名:<span sec:authentication="name"></span></p>
    <p>角色:<span sec:authentication="principal.authorities"></span></p>
    <p>Username:<span sec:authentication="principal.username"></span></p>

    <div sec:authorize="hasAuthority('ROLE_user:view:GET')">用户列表</div>
    <div sec:authorize="hasRole('user:add:POST')">添加用户</div>
    <div sec:authorize="hasAuthority('ROLE_user:update:PUT')">修改用户</div>
    <div sec:authorize="hasAuthority('ROLE_user:delete:DELETE')">删除用户</div>
</div>
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <title>登录</title>
</head>
<body>
<form method="post" action="/login">
    <h2 class="form-signin-heading">登录</h2>
    <span th:if="${param.error}" th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"></span>
    <p>
        <label for="username">用户名</label>
        <input type="text" id="username" name="username" required autofocus>
    </p>
    <p>
        <label for="password">密码</label>
        <input type="password" id="password" name="password" required>
    </p>

    <button type="submit">登录</button>
</form>
</body>
</html>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值