之前没有做过权限方面的,看了几篇博客,感觉写的不够清晰,自己总结一下,仅当做自己学习的记录,如有错误请指出。
org.springframework.boot
spring-boot-starter-security
org.thymeleaf.extras
thymeleaf-extras-springsecurity4
net.sourceforge.nekohtml
nekohtml
thymeleaf-extras-springsecurity4作用是在thymeleaf模板页面中可使用
#默认html5,格式要求严格,LEGACYHTML5格式要求不严格(需要添加nokehtml的包依赖才能使用)
spring.thymeleaf.mode = LEGACYHTML5
在进入后面的代码编写之前,首先看一下Spring Security的执行原理与流程:
原理:使用很多的拦截器对URL进行拦截,以此来管理登录验证和用户权限验证。
流程:用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装成一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。
请求携带用户名密码->(Authentication(未认证) -> AuthenticationManager ->AuthenticationProvider->UserDetailsService->UserDetails->return Authentication(已认证,登录成功的token)
校验原理:Spring Security 校验的原理:左手配置信息,右手登录后的用户信息,中间投票器。从我们的配置信息中获取相关的URL和需要的权限信息,然后获得登录后的用户信息,然后经过:AccessDecisionManager 来验证,这里面有多个投票器:AccessDecisionVoter,(默认有几种实现:比如:1票否决(只要有一个不同意,就没有权限),全票通过,才算通过;只要有1个通过,就全部通过。类似这种的。WebExpressionVoter 是Spring Security默认提供的的web开发的投票器。(表达式的投票器)Spring Security 默认的是 AffirmativeBased 只要有一个通过,就通过。
SpringBoot中,默认的Spring Security就是生效的。(即引入支持包就会生效了)
配置哪些页面访问需要登录、登录页面、登录成功处理者、登录失败处理者等等:
package www.yzh.com.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import www.yzh.com.component.MyAuthenticationProvider;
/**
* 我的安全配置
* Title: MySecurityConfig
* Description:
* @author yzh
* @date 2018年4月26日上午11:44:17
*/
@Configuration
@EnableWebSecurity//@EnableWebMvcSecurity 注解开启Spring Security的验证
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyAuthenticationProvider myAuthenticationProvider;
@Autowired
MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
//通过重载,配置如何通过拦截器保护请求
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/hello","/hi").permitAll()//hello,hi请求允许任何人访问(必须配置在.anyRequest().authenticated()之前)
.antMatchers("/testrole").hasRole("smalladmin")//有smalladmin的role才能访问
.anyRequest()//.access(attribute)//自定义验证
.authenticated()//其它所有请求都需要验证
.and().formLogin().loginPage("/login.html")//登录页面
.loginProcessingUrl("/login")//登录接口
.successHandler(myAuthenticationSuccessHandler)//登陆成功自定义处理
.failureUrl("/login?error")//登录失败接口
.failureHandler(myAuthenticationFailureHandler)//登陆失败处理
.permitAll()//允许所有请求(不需要验证)
.and().logout().permitAll()//注销(不需要验证)
.and().rememberMe()//开启cookie保存用户数据
.and().csrf().disable();//关闭csrf(跨站请求伪造)防护,@EnableWebSecurity默认为表单开启了;若开启,
用来获取(从数据库中)封装用户信息:
package www.yzh.com.component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import www.yzh.com.dao.AdminDao;
import www.yzh.com.entity.Admin;
import www.yzh.com.entity.AdminDto;
/**
* 用户详细服务
* Title: MyUserDetailsService
* Description:
* @author yzh
* @date 2018年4月26日下午12:38:19
*/
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
AdminDao adminDao;
/**
* 这里返回的UserDetails是为了和用户输入的账号进行比较(并不是返回的结果就是登陆是否成功的标识)
*/
@Override
public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException {
Admin admin = adminDao.selectByName(username);
if(admin != null){
AdminDto adminDto = new AdminDto(admin.getName(), admin.getPassword(), admin.getRoleId(), "ROLE_bigadmin", true, true, true, true);
return adminDto;
}
return null;
}
}
其中Admin:
package www.yzh.com.entity;
public class Admin extends BaseEntity{
private Integer id;
private String name;
private String password;
private Integer roleId;
private static final long serialVersionUID = 1L;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password == null ? null : password.trim();
}
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", name=").append(name);
sb.append(", password=").append(password);
sb.append(", roleId=").append(roleId);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
public Admin() {
super();
}
public Admin(Integer id, String name, String password, Integer roleId) {
super();
this.id = id;
this.name = name;
this.password = password;
this.roleId = roleId;
}
}
其中AdminDto:
package www.yzh.com.entity;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
public class AdminDto extends Admin implements UserDetails{
private String name;
private String password;
private Integer roleId;
private String role;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
private static final long serialVersionUID = 1L;
public AdminDto( String name, String password, Integer roleId,
String role, boolean accountNonExpired, boolean accountNonLocked,
boolean credentialsNonExpired, boolean enabled) {
super();
this.name = name;
this.password = password;
this.roleId = roleId;
this.role = role;
this.accountNonExpired = accountNonExpired;
this.accountNonLocked = accountNonLocked;
this.credentialsNonExpired = credentialsNonExpired;
this.enabled = enabled;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public void setAccountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
public void setAccountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password == null ? null : password.trim();
}
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
@Override
public String toString() {
return "AdminDto [name=" + name + ", password="
+ password + ", roleId=" + roleId + ", role=" + role
+ ", accountNonExpired=" + accountNonExpired
+ ", accountNonLocked=" + accountNonLocked
+ ", credentialsNonExpired=" + credentialsNonExpired
+ ", enabled=" + enabled + "]";
}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
List list = AuthorityUtils.commaSeparatedStringToAuthorityList(role);
return list;
}
@Override
public String getUsername() {
// TODO Auto-generated method stub
return name;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return enabled;
}
}
验证用户名密码是否正确以及错误信息提示,返回登录成功的认证标识Authentication:
package www.yzh.com.component;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
/**
* 自己的认证提供者
* Title: MyAuthenticationProvider
* Description:
* @author yzh
* @date 2018年4月26日下午5:19:57
*/
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
@Autowired
MyUserDetailsService myUserDetailsService;
@Override
public Authentication authenticate(Authentication authentication)throws AuthenticationException {
String name = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
UserDetails userDetails = myUserDetailsService.loadUserByUsername(name);
if(userDetails == null){
throw new UsernameNotFoundException("用户名不存在");
}
if(!userDetails.getPassword().equals(password)){
throw new BadCredentialsException("密码不正确");
}
Collection extends GrantedAuthority> authorities = userDetails.getAuthorities();
//构建返回用户登录成功的token
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, password, authorities);
return token;
}
@Override
public boolean supports(Class> authentication) {
// TODO Auto-generated method stub
//表示支持
return true;
}
}
package www.yzh.com.configuration;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 我的认证成功后的处理
* Title: MyAuthenticationSuccessHandler
* Description:
* @author yzh
* @date 2018年4月27日上午11:03:54
*/
@Component(value="myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Autowired
ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
//登陆成功后自己的业务逻辑
System.out.println("登陆成功");
//返回json
// response.setContentType("application/json;charset=UTF-8");
// response.getWriter().write(objectMapper.writeValueAsString(authentication));
//如果是要跳转到某个页面
new DefaultRedirectStrategy().sendRedirect(request, response, "/index");
}
}
package www.yzh.com.configuration;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 我的认证失败后的处理
* Title: MyAuthenticationSuccessHandler
* Description:
* @author yzh
* @date 2018年4月27日上午11:03:54
*/
@Component(value="myAuthenticationFailureHandler")
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired
ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
// TODO Auto-generated method stub
// super.onAuthenticationFailure(request, response, exception);
System.out.println("登陆失败");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));
}
}
登录页面:
Insert title here
登录成功页面:
Insert title here
index 你能看见我吗?
有更多显示信息
hi
testrole
控制层代码:
package www.yzh.com.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloSecurityController {
@RequestMapping("/hello")
public String hello(Model model){
return "hello";
}
@RequestMapping("/hi")
public String hi(Model model){
return "hello";
}
@RequestMapping("/testrole")
public String testrole(Model model){
return "hello";
}
@RequestMapping("/")
public String success(Model model){
return "redirect:/index";
}
@RequestMapping("/index")
public String index(Model model){
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("authentication", authentication);
Object credentials = authentication.getCredentials();
model.addAttribute("credentials", credentials);
model.addAttribute("principal", principal);
return "index";
}
@RequestMapping("/login")
public String login(Model model,String error){
model.addAttribute("error", error);
return "login";
}
}
后端代码中,权限信息,用户信息,角色信息都应该从数据库中获取就不再展示代码。