Spring Security(二):认证(Authentication)-用户名密码登录

此示例是使用Spring Security实现登录功能。

1.pom.xml

thymeleaf-extras-springsecurity5是springsecurity针对thymeleaf模板引擎而开发的一套标签库,可以在thymeleaf中引入该标签库,然后就可以在thymeleaf中使用springsecurity提供的标签了。

注意:Spring Boot 2.x 要用thymeleaf-extras-springsecurity5; Spring Boot 1.x用thymeleaf-extras-springsecurity4

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-security-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-security-example</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. 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>

index.html

引入标签库xmlns:sec=“http://www.thymeleaf.org/thymeleaf-extras-springsecurity4”,引入标签库这里使用thymeleaf-extras-springsecurity4,标签库就是对应于org.springframework.security.access.expression.SecurityExpressionRoot这个类。

常用的标签库

  • sec:authorize 权限
  • sec:authentication 认证
  • sec:authorize-url : 不能直接使用还需额外的配置 (也不建议使用该标签,因为该标签不支持restful风格)

SecurityExpressionRoot常用的表达式

  • boolean hasAuthority(String authority)
  • boolean hasAnyAuthority(String… authorities)
  • boolean hasRole(String role)
  • hasAnyRole(String… roles)
  • Authentication getAuthentication()
  • Object getPrincipal()
  • boolean isAnonymous()
  • boolean isAuthenticated()
  • boolean isRememberMe()
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      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>

userList.html

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

orderList.html

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

3. WebMvcConfiguration

@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");
    }
}

4. model

权限实体实现GrantedAuthority接口,重写getAuthority方法,该方法唯一标识一个权限

@Data
@ToString
@AllArgsConstructor
@RequiredArgsConstructor
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
@ToString
@AllArgsConstructor
@RequiredArgsConstructor
public class SysUser {
    private Long id;
    private String username;
    private String password;

    private List<SysPermission> sysPermissions;
}

5. UserDetailsService

从数据库中获取用户的信息和权限列表

@Component
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 模拟根据用户名查询用户信息和权限
        SysUser user = new SysUser(1L, "admin", "$2a$10$nm5H9QvnoWao.l7NbxQGZeZoR0Cn.VqCpsl3E/FhglPa954Zg9ccm", Arrays.asList(
                new SysPermission(1L, "用户列表", "user:view", "/getUserList", "GET"),
                new SysPermission(2L, "添加用户", "user:add", "/addUser", "POST"),
                new SysPermission(3L, "修改用户", "user:update", "/updateUser", "PUT")
        ));
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }

        return new User(user.getUsername(), user.getPassword(), user.getSysPermissions());
    }

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

登录成功后处理的逻辑,默认是重定向到上一个url,也可以在这里自定义自己的逻辑。

@Slf4j
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("login sucesssful {}", objectMapper.writeValueAsString(authentication));

        response.sendRedirect("/index");
    }
}

Spring Security的核心配置,注意BCryptPasswordEncoder是用于加密解密密码的,每次加密的密文都不一样。

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                // 配置需要认证的请求
                .authorizeRequests()
                    .anyRequest()
                    .authenticated()
                    .and()
                // 登录表单相关配置
                .formLogin()
                    .loginPage("/login")
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .successHandler(myAuthenticationSuccessHandler)
                    .failureUrl("/login?error")
                    .permitAll()
                    .and()
                // 登出相关配置
                .logout()
                    .permitAll();

    }

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

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/static/**");
    }

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

6. 运行结果

  1. 访问http://localhost:8080/getUserList跳转到登录页面http://localhost:8080/login
  2. 输入用户名密码登录成功后跳转到首页index.html(登录成功后的逻辑是在AuthenticationSuccessHandler中处理的),因admin用户只有用户列表、添加用户、修改用户 所以在页面上只能看到这三个按钮没有删除用户的按钮。
    在这里插入图片描述
  3. 访问http://localhost:8080/getOrderList可以看到订单页面,虽然用户的权限列表中并没有订单相关的权限,但是仍然能访问。此问题属于权限校验,在后面解决这个问题。
    在这里插入图片描述
发布了308 篇原创文章 · 获赞 936 · 访问量 133万+

猜你喜欢

转载自blog.csdn.net/vbirdbest/article/details/90145383