SpringSecurity(二):自定义登录流程

552 阅读2分钟

1.自定义CustomUserDetailsService实现UserDetailsService接口
一个用户可以拥有多个角色,一个角色可以拥有多个权限

@Slf4j
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private PermissionMapper permissionMapper;

    public static void main(String[] args) {
        //一种基于随机生成salt的根据强大的哈希加密算法。
        String encode = new BCryptPasswordEncoder().encode("123456");
        System.out.println(new BCryptPasswordEncoder().matches("123", encode));
        System.out.println(encode);
    }

    /**
     * 从数据库中根据用户名查询数据,这个时候要求用户名唯一
     */
    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = null;
        try {
            user = userMapper.selectOne(new QueryWrapper<User>(new User().setUsername(username)));
        } catch (Exception e) {
            throw new CustomException("用户名重复");
        }
        if (Objects.isNull(user)) {
            throw new CustomException("用户不存在");
        }
        List<Role> roles = roleMapper.getRolesByUserId(user.getId());
        List<Long> roleIds = roles.stream().map(Role::getId).collect(Collectors.toList());
        List<String> permissions = permissionMapper.getPermissionByRoles(roleIds);
        user.setRoles(roles).setPermissionIds(permissions);
        List<GrantedAuthority> authorities = new ArrayList<>(40);
        permissions.forEach(item -> {
            authorities.add(new SimpleGrantedAuthority(item));
        });
        log.info(user.getUsername() + "所有拥有的权限{}", authorities.toString());
        // security框架自己会将前端提交的密码和user.getPassword()进行比较,如果通过则跳转至成功页面,否则就会重定向到登录页面
        UserDetails userDetails = new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
        return userDetails;
    }
}

2.roleMapper代码

# 通过用户id获取所有的角色信息
List<Role> getRolesByUserId(Long id);

<select id="getRolesByUserId" resultMap="BaseResultMap">
    select tr.*
    from t_user u
    left join t_user_role tur on u.id = tur.user_id
    left join t_role tr on tur.role_id = tr.id
    where u.id = ${id}
</select>

3.permissionMapper代码

# 通过上述获取的角色ids进而获取权限资源
List<String> getPermissionByRoles(@Param("roleIds") List<Long> roleIds);

<select id="getPermissionByRoles" resultType="string" parameterType="list">
    SELECT distinct(TP.permission_url)
    FROM T_ROLE_PERMISSION TRP
             LEFT JOIN T_ROLE TR ON TRP.ROLE_ID = TR.ID
             LEFT JOIN T_PERMISSION TP ON TRP.PERMISSION_ID = TP.ID
    where TR.id in
    <foreach collection="roleIds" item="roleId" separator="," open="(" close=")" index="">
        #{roleId,jdbcType=INTEGER}
    </foreach>
</select>

4.User实体

@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("t_user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用户表主键
     */
    @TableId(value = "id", type = IdType.INPUT)
    private Long id;

    /**
     * 用户名称
     */
    private String username;

    /**
     * 用户密码
     */
    private String password;

    /**
     * 用户状态(1:启动,0:禁用)
     */
    private Boolean state;

    /**
     * 角色
     */
    @TableField(exist = false)
    private List<Role> roles;
    /**
     * 权限标识
     */
    @TableField(exist = false)
    private List<String> permissionIds;
}

5.WebSecurityConfig文件

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启方法权限控制
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

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

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        // 禁用csrf
        httpSecurity.csrf().disable();

        // 设置白名单和需要权限访问的接口
        httpSecurity.authorizeRequests()
                // 放掉所有预检请求
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .antMatchers("/toLogin", "/toMain", "/toError").permitAll()
                .anyRequest().authenticated();

        // 登录
        httpSecurity.formLogin()
                // 登录界面的请求地址
                .loginPage("/toLogin")
                // 这个地址要和表单中action地址一致,才可以通过security进行登录
                .loginProcessingUrl("/auth/login")
                // 登录成功以后所跳转的地址,必须是post请求
                //.successForwardUrl("/toMain")
                // 设置handel,都是前后分离的,所以可以自己创建Handel
                .successHandler(new LoginSuccessHandle("/toMain"))
                // 登录失败跳转的地址,必须是post请求,要将这个地址进行放行处理
                .failureUrl("/toError");

        // 退出登录
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessUrl("/toLogin");

        // 禁用缓存
        httpSecurity.headers().cacheControl();
    }
}

6.界面

登录界面:
<body>
<h2>欢迎登录</h2>
<form action="/auth/login" method="post">
    <input name="username" type="text" placeholder="请输入用户名.."><br/>
    <input name="password" type="password" placeholder="请输入密码.."><br/>
    <input type="submit" value="登录">
</form>
</body>

登录成功界面:
<body>
登录成功
</body>

登录失败页面:
<body>
操作失败<a href="/toLogin">返回登录页</a>
</body>

7.请求接口

@Controller
public class Login {

    @GetMapping("/toLogin")
    public String toLogin() {
        return "login";
    }

    @GetMapping("/toMain")
    public String toMain() {
        return "main";
    }

    @PostMapping("/toError")
    public String toError() {
        return "error";
    }


    @GetMapping("/logout")
    public void logout() {
    }
}