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() {
}
}