目录
一. 实现WebMvcConfigurer接口
二. 实现GrantedAuthority接口
三. 定义实体(附带权限集合)
四. 实现AuthenticationSuccessHandler
五. 实现UserDetailsService接口
六. 继承WebSecurityConfigurerAdapter
七. 启动类及前端页面
java代码以及html页面 copy from https://blog.csdn.net/vbirdbest/article/details/90145383
其他皆为原创。
pom.xml如下:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-security
org.thymeleaf.extras
thymeleaf-extras-springsecurity5
org.springframework.boot
spring-boot-starter-thymeleaf
首先来配置SpringMVC相关的内容。通过实现WebMvcConfigurer的addViewControllers()方法
1. 对于WebMvcConfigurer,看下它的源码注释。如下:
/**
* Defines callback methods to customize the Java-based configuration for
* Spring MVC enabled via {@code @EnableWebMvc}.
*
* {@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.
* 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.
* 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 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.
*
* 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}.
*
*
* 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 UserDetails
* 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 null
)
*
* @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
订单列表
userList.html
用户列表
index.html
登录名:
角色:
Username:
用户列表
添加用户
修改用户
删除用户
login.html
登录