简介
本博客主要针对的是,SpringBoot2与Sercurity 5.x的整合,其中利用的技术如下
- 自定义认证类
- 链接数据库访问
- 用户的角色变更,但是角色权限不能变更
- 一些错误分析以及解决
- 基于注解实现的权限控制
整体思路如下
- 导入依赖的pom文件
- 确定资源访问的路径以及权限
- 配置Sercurity
- 实现自定义认证类
- 实现数据库访问
POM文件
<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.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.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
确定页面权限如下
- 其中index页面和login页面不设置权限,所有用户均可访问
- Welcome页面,只有登录过后的用户可以访问
- admin页面,只有拥有“admin”权限的用户可以访问
- ceshi页面,只有拥有“ceshi”权限的用户可以访问
- 403页面,是因为权限不够,会报状态码403的错误,springboot默认,在error文件夹下创建状态码文件即可处理错误页面,无需多的配置
Controller文件
@Controller
public class UserController {
@RequestMapping(value = { "", "/index" }, method = RequestMethod.GET)
public String home() {
return "index";
}
@RequestMapping(value = { "/ceshi" }, method = RequestMethod.GET)
public String ceshi() {
return "ceshi";
}
@RequestMapping(value = "/welcome", method = RequestMethod.GET)
public String userPage() {
return "welcome";
}
@RequestMapping(value = "/admin", method = RequestMethod.GET)
public String adminPage() {
return "admin";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "login";
}
}
配置sercurity配置文件
@Configuration
@EnableWebSecurity
public class MySercurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public MyUserDetailsService myUserDetailsService(){
return new MyUserDetailsService();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService()).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/","/index")
.permitAll()
.antMatchers("/admin").access("hasRole('admin')")
.antMatchers("/welcome").hasAnyRole("admin","user")
.antMatchers("/ceshi").access("hasRole('ceshi')")
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/welcome")
.permitAll()
.and()
.logout()
.permitAll();
}
}
-
针对前面的两个bean下面会给出详细讲解,这段先不说
-
configure(AuthenticationManagerBuilder auth)
重写的这个方法,就是我们的认证方法,也就是我们接下来需要自定义用户认证类的方法 -
configure(HttpSecurity http)
这个就是权限拦截的方法,接下来对里面的配置进行讲解 -
http.authorizeRequests()
这段代码是指,通过这个方法为所有的请求进行权限配置 -
antMatchers("/","/index") .permitAll()
这一行代码是值针对某些路径直接放行,所有用户都可以访问,主要用于我们的首页、静态资源、登录注册等 -
antMatchers("/admin").access("hasRole('admin')")
这一行代码一样的,就是针对某些路径,只允许具备这个角色访问,也就是对角色的权限进行分配了(ps:这里需要注意,SpringSercurity会自动帮我们拼接“ROLE_”,也就是我们实际上是“ROLE_admin”而不是“admin”) -
hasAnyRole("admin","user")
这行代码,就是指针对某些路径,可以多个角色一起访问,都具备访问资格 -
formLogin() .loginPage("/login") .defaultSuccessUrl("/welcome")
这一行代码,也很好理解,就是开启我们的登录页面,如果我们不要loginPage("/login")
这个的话,就是使用Sercurity默认的登录页面,同时我们需要注意,我们自定义的页面,用户名和密码只能是(username,password)否则,你需要在这里进行别名配置,后面defaultSuccessUrl("/welcome")
就是登录成功默认跳转的界面,安然也有登录失败跳转的界面了 -
logout() .permitAll();
这最后一句,就更容易理解了,也就是一个退出按钮而已,但是,我们需要注意,我们的页面提交按钮应该如下
<form th:action="@{/logout}" method="post"> <input type="submit" value="登出"/> </form>
自定义认证类
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user=userDao.getUser(s);
if (user==null){
throw new UsernameNotFoundException("not found");
}else {
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
for (String role:user.getRole()) {
grantedAuths.add(new SimpleGrantedAuthority(role));
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuths);
}
}
}
数据库访问(模拟实现,没真实链接)
@Repository
public class UserDaoImpl implements UserDao {
private static Map<String, User> userMap=new HashMap<>();
static{
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String str=passwordEncoder.encode("123456");
System.out.println(str);
User user=new User("zhangsan",str, Arrays.asList("ROLE_admin","ROLE_ceshi"));
userMap.put(user.getUsername(),user);
user=new User("lisi",str, Arrays.asList("ROLE_user"));
userMap.put(user.getUsername(),user);
}
@Override
public User getUser(String username) {
return userMap.get(username);
}
}
- 从上面的两个方法,就可以看出,自定义认证类其实很简单,就是实现一个
UserDetailsService
接口,然后重写里面的方法就ok User user=userDao.getUser(s);
这里首先获取我们登陆的用户,然后进行判断,看是否为空- 如果为空,直接抛出异常
- 不为空,我们就新建一个List,以及一个User
- 其实,这个写法相对比较固定,
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
首先新建一个角色链表,获取当前用户所有的角色,然后再返回一个User对象也就好了
自定义认证类的配置
-
我们首先完成了自定义认证类的编写,同时绑定了数据库的链接,但是这是不够,我们还需要吧这个类注入到配置文件中
-
这就到了我们上一步涉及到了两个Bean了
@Bean public MyUserDetailsService myUserDetailsService(){ return new MyUserDetailsService(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService()).passwordEncoder(passwordEncoder()); }
- 这里就是将我们自定义的认证类注入进入,让Sercurity的配置类可以读取到
- 其中的PasswordEncoder,是密码加密的,因为是Sercurity 5.X中必须使用的,不然会出错,下面会截图说明
总结
通过以上的几个步骤,已经简单的完成了一个基于数据库的整合了,同时,你也可以自己写一个修改角色的方法,因为这里的角色都是数据库查出来的,所以可以灵活更改
下面我给出一些错误截图,以及解决方案
错误一:
这个错误,就是因为Sercurity 5.X中,必须注入PasswordEncoder,我们只需要将以下代码加入就可以
错误二:
这个错误,是因为我们在保存的时候,没有保存为BCryptPasswordEncoder的形式,解决方法如下
对于权限的配置,还有一种基于注解的方式
对于这种基于注解的方式,好像是Sercurity 4.X开始的,具体不清楚,具体配置如下
老配置
注意我这里红线框出来的部分
新配置
源代码地址
友情提示
${#httpServletRequest.remoteUser}]
这个方法的返回值,参考API文档给出的如下解释