@wangzhuanyun
2020-09-22T07:29:21.000000Z
字数 7167
阅读 1341
springboot
Spring Security 提供了基于javaEE的企业应有个你软件全面的安全服务。这里特别强调支持使用SPring框架构件的项目,Spring框架是企业软件开发javaEE方案的领导者。如果你还没有使用Spring来开发企业应用程序,我们热忱的鼓励你仔细的看一看。熟悉Spring特别是一来注入原理两帮助你更快更方便的使用Spring Security
正如你可能知道的两个应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。“认证”,是建立一个他声明的主题的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。“授权”指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。这个概念是通用的而不只在Spring Security中
认证(验证)就是确认用户的身份,一般采用用户名和密码的形式;授权(鉴权)就是确认用户拥有的身份(角色、权限)能否访问受保护的资源
我们创建一个 HelloController:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
访问 /hello ,需要登录之后才能访问。
默认用户名为user 密码则是项目启动时随机生成的字符串,可以从启动的控制台日志中看到默认密码
这个随机生成的密码,每次启动时都会变。对登录的用户名/密码进行配置,有三种不同的方式:
可以直接在 application.properties 文件中配置用户的基本信息:
spring.security.user.name=user
spring.security.user.password=123
配置完成后,重启项目,就可以使用这里配置的用户名/密码登录了。
也可以在 Java 代码中配置用户名密码,首先需要我们创建一个 Spring Security 的配置类,继承自 WebSecurityConfigurerAdapter 类,重写configure方法
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置用户名 密码 及用户角色(角色目前无用)
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).withUser("wzy").
password(passwordEncoder().encode("123")).roles("USER");
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
从 Spring5 开始,强制要求密码要加密,如果非不想加密,可以使用一个过期的 PasswordEncoder 的实例 NoOpPasswordEncoder,但是不建议这么做,毕竟不安全。
Spring Security 中提供了 BCryptPasswordEncoder 密码编码工具,可以非常方便的实现密码的加密加盐,相同明文加密出来的结果总是不同,这样就不需要用户去额外保存盐的字段。
再次登录输入设置的用户名wzy与密码123即可登录。
创建LoginAuthenticationProvider类,实现接口AuthenticationProvider
@Component
public class LoginAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = (String) authentication.getPrincipal(); //拿到username
String password = (String) authentication.getCredentials(); //拿到password
System.out.println("userName========"+userName);
System.out.println("password========"+password);
//调用service层 连接数据库 返回登录对象 此处忽略
SfAdmin sfAdmin = new SfAdmin();
sfAdmin.setAdmin_pwd(password);
sfAdmin.setAdmin_name(userName);
//判断是否登录 未登录不进入
if(sfAdmin!=null)
{
//设置的权限必须以 ROLE开头
Collection<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN");
return new UsernamePasswordAuthenticationToken(sfAdmin, sfAdmin.getAdmin_pwd(),authorities);
}
/*验证不通过*/
return null;
}
/**
* 如果该AuthenticationProvider支持传入的Authentication对象,则返回true
* @param aClass
* @return
*/
@Override
public boolean supports(Class<?> aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
修改SecurityConfig配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//实例化过滤器
@Autowired
private LoginAuthenticationProvider provider;
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//登录调用provider
auth.authenticationProvider(provider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭csrf防护
.csrf().disable()
.headers().frameOptions().disable()
.and();
http
//登录处理
.formLogin() //表单方式,或httpBasic
.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/form") //表单提交
.defaultSuccessUrl("/admin/hello") //成功登陆后跳转页面
.permitAll(); //放行以上路径
http.authorizeRequests()
.antMatchers("/").permitAll() //放行这个请求
.antMatchers("/admin/**").hasRole("ADMIN") //admin下的页面需要ADMIN这个角色才可以访问
.antMatchers("/user/**").hasRole("USER");
}
编写Controller进行测试
@Controller
@RequestMapping("/admin")
public class IndexController {
@RequestMapping("/hello")
public String hello(){
return "hello";
}
}
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/hello")
public String security(){
return "hello";
}
}
创建类进行登录拦截RbacAuthorityService
@Component("rbacauthorityservice")
public class RbacAuthorityService {
@Resource
private Environment env;
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 返回false时 调用AjaxAuthenticationEntryPoint类
* @param request
* @param authentication
* @return
*/
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
// 获取当前请求的URI
String requestURI = request.getServletPath();
// 放开登录url
if (requestURI.equals("/admin/login")){
return true;
}
// 登录判断
String token = request.getHeader("userToken");
//token需要到redis中找 找不到也是未登录
System.out.println(token);
if (token == null ){
request.setAttribute("flagName","未登录");
return false;
}
// 权限判断 利用token获取用户登录对象 获取角色ID 通过角色ID到redis中找角色对应的权限集合
// 利用token去Redis取出当前角色的权限,这里就直接写死了
List<String> roles = new ArrayList<>();
roles.add("/admin/list");
roles.add("/admin/menu");
if (!roles.contains(requestURI)){
request.setAttribute("flagName","权限不足");
return false;
}
return true;
}
}
创建AjaxAuthenticationEntryPoint类实现AuthenticationEntryPoint接口,统一返回JSON给前端
/**
* 用户没有登录时返回给前端的数据
*/
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
String msgName = httpServletRequest.getAttribute("msgName").toString();
MassageJson massageJson = new MassageJson();
if (msgName.equals("未登录")){
massageJson.setCode(001);
massageJson.setMsg("未登录");
} else if (msgName.equals("权限不足")){
massageJson.setCode(002);
massageJson.setMsg("权限不足");
} else{
massageJson.setCode(003);
massageJson.setMsg("系统异常");
}
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(massageJson));
}
}
修改配置类SecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 未登陆时返回 JSON 格式的数据给前端
*/
@Resource
AjaxAuthenticationEntryPoint ajaxAuthenticationEntryPoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 去掉 CSRF
http.csrf().disable()
.exceptionHandling().authenticationEntryPoint(ajaxAuthenticationEntryPoint)
.and()
// 基于Token 不需要Session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 登录处理
.and()
.formLogin()
.loginProcessingUrl("admin/login")
.permitAll()
// 登录和权限控制
.and()
.authorizeRequests()
.anyRequest()
// RBAC 动态 url 认证
.access("@rbacauthorityservice.hasPermission(request,authentication)");
}
}
创建AjaxLogoutSuccessHandler类实现接口LogoutSuccessHandler,进行注销
/**
* 退出登录
*/
@Component
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// 从这里拿到token 然后把这个token注销
String token = httpServletRequest.getHeader("userToken");
// 去redis删除token
stringRedisTemplate.delete(token);
MassageJson massageJson = new MassageJson();
massageJson.setCode(004);
massageJson.setMsg("退出成功!");
httpServletResponse.setContentType("text/html;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(massageJson));
}
}
修改SecurityConfig配置类
/**
* 注销成功返回的 JSON 格式数据给前端
*/
@Resource
AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
//在configure方法下添加 注销处理
.and()
.logout()//默认注销行为为logout
.logoutUrl("/admin/loginOut") //设置调用路径
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.permitAll();